+
No appointments on this day.
)}
diff --git a/frontend/components/messages/messages-view.tsx b/frontend/components/messages/messages-view.tsx
index 3feecf8..6884d10 100644
--- a/frontend/components/messages/messages-view.tsx
+++ b/frontend/components/messages/messages-view.tsx
@@ -1,7 +1,7 @@
"use client";
import { Mail, SendHorizonal } from "lucide-react";
-import { useState } from "react";
+import { type FormEvent, useMemo, useState } from "react";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
@@ -12,198 +12,287 @@ import {
EmptyMedia,
EmptyTitle,
} from "@/components/ui/empty";
-import { Textarea } from "@/components/ui/textarea";
-import { notify } from "@/lib/toast";
+import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
-// All messages here are mock/placeholder data — there is no messaging backend.
-// They illustrate the email-style inbox layout (left list, right reading pane).
+// All conversations here are mock/placeholder data — there is no messaging
+// backend. They illustrate the inbox + chat-thread timeline layout.
-type Message = {
+type ChatMessage = {
id: string;
- sender: string;
- initials: string;
- subject: string;
- preview: string;
- body: string[];
+ direction: "in" | "out";
+ text: string;
time: string;
- read: boolean;
};
-const seed: Message[] = [
+type Conversation = {
+ id: string;
+ name: string;
+ initials: string;
+ role: string;
+ unread: boolean;
+ messages: ChatMessage[];
+};
+
+const seed: Conversation[] = [
{
id: "1",
- sender: "Dr. Stein",
+ name: "Dr. Stein",
initials: "DS",
- subject: "Lab results ready",
- preview: "The lab results for Amina Yusuf are now available…",
- body: [
- "Hi,",
- "The lab results for Amina Yusuf (file #10293) are now available for your review. The lipid panel and HbA1c both came back within the expected range.",
- "Let me know if you'd like to adjust her current plan before the follow-up.",
- "— Dr. Stein",
+ role: "Endocrinology",
+ unread: true,
+ messages: [
+ {
+ id: "1a",
+ direction: "in",
+ text: "The lab results for Amina Yusuf are back — lipid panel and HbA1c are both in range.",
+ time: "10:24",
+ },
+ {
+ id: "1b",
+ direction: "in",
+ text: "Want me to adjust her plan before the follow-up?",
+ time: "10:25",
+ },
],
- time: "10:24",
- read: false,
},
{
id: "2",
- sender: "Dr. Okafor",
+ name: "Dr. Okafor",
initials: "DO",
- subject: "Re: Daniel Mensah intake",
- preview: "Thanks for sending the intake notes over. I've…",
- body: [
- "Thanks for sending the intake notes over.",
- "I've added him to tomorrow's schedule at 10:00. Could you confirm whether his prior records were imported?",
- "— Dr. Okafor",
+ role: "Family medicine",
+ unread: true,
+ messages: [
+ {
+ id: "2a",
+ direction: "out",
+ text: "Sent over Daniel Mensah's intake notes — can you take a look?",
+ time: "08:50",
+ },
+ {
+ id: "2b",
+ direction: "in",
+ text: "Thanks! Added him to tomorrow at 10:00. Were his prior records imported?",
+ time: "09:12",
+ },
],
- time: "09:12",
- read: false,
},
{
id: "3",
- sender: "Care team",
+ name: "Care team",
initials: "CT",
- subject: "Vaccination stock update",
- preview: "A reminder that the seasonal vaccine stock has…",
- body: [
- "A reminder that the seasonal vaccine stock has been replenished.",
- "Slots are open across the week — please direct eligible patients to the front desk to book.",
+ role: "Clinic-wide",
+ unread: false,
+ messages: [
+ {
+ id: "3a",
+ direction: "in",
+ text: "Seasonal vaccine stock has been replenished — slots are open all week.",
+ time: "Yesterday",
+ },
+ {
+ id: "3b",
+ direction: "out",
+ text: "Great, I'll direct eligible patients to the front desk.",
+ time: "Yesterday",
+ },
],
- time: "Yesterday",
- read: true,
},
{
id: "4",
- sender: "Reception",
+ name: "Reception",
initials: "RC",
- subject: "Schedule change for Friday",
- preview: "Two afternoon appointments were rescheduled…",
- body: [
- "Two afternoon appointments on Friday were rescheduled to next Monday at the patients' request.",
- "The updated times are reflected in the schedule.",
+ role: "Front desk",
+ unread: false,
+ messages: [
+ {
+ id: "4a",
+ direction: "in",
+ text: "Two Friday afternoon appointments were moved to next Monday at the patients' request.",
+ time: "Mon",
+ },
],
- time: "Mon",
- read: true,
},
];
-export function MessagesView() {
- const [messages, setMessages] = useState
(seed);
- const [selectedId, setSelectedId] = useState(null);
- const [reply, setReply] = useState("");
+const now = () =>
+ new Date().toLocaleTimeString("en-US", {
+ hour: "2-digit",
+ minute: "2-digit",
+ hour12: false,
+ });
- const selected = messages.find((m) => m.id === selectedId) ?? null;
+export function MessagesView() {
+ const [conversations, setConversations] = useState(seed);
+ const [selectedId, setSelectedId] = useState(null);
+ const [showUnreadOnly, setShowUnreadOnly] = useState(false);
+ const [draft, setDraft] = useState("");
+
+ const unreadCount = conversations.filter((c) => c.unread).length;
+ const selected = conversations.find((c) => c.id === selectedId) ?? null;
+
+ const visible = useMemo(
+ () => (showUnreadOnly ? conversations.filter((c) => c.unread) : conversations),
+ [conversations, showUnreadOnly],
+ );
const open = (id: string) => {
setSelectedId(id);
- setReply("");
- setMessages((prev) =>
- prev.map((m) => (m.id === id ? { ...m, read: true } : m)),
+ setDraft("");
+ setConversations((prev) =>
+ prev.map((c) => (c.id === id ? { ...c, unread: false } : c)),
);
};
- const send = () => {
- if (!(reply.trim() && selected)) return;
- notify.success("Reply sent", `To ${selected.sender}`);
- setReply("");
+ const send = (event: FormEvent) => {
+ event.preventDefault();
+ const text = draft.trim();
+ if (!(text && selected)) return;
+ const message: ChatMessage = {
+ id: `${selected.id}-${Date.now()}`,
+ direction: "out",
+ text,
+ time: now(),
+ };
+ setConversations((prev) =>
+ prev.map((c) =>
+ c.id === selected.id
+ ? { ...c, messages: [...c.messages, message] }
+ : c,
+ ),
+ );
+ setDraft("");
};
return (
- {/* Left: inbox list */}
+ {/* Left: conversation list */}
- {/* Right: reading pane or empty state */}
+ {/* Right: conversation timeline or empty state */}
{selected ? (
-
-
- {selected.subject}
-
-
-
- {selected.initials}
-
-
-
- {selected.sender}
-
-
- to me · {selected.time}
-
-
+
+
+ {selected.initials}
+
+
+
+ {selected.name}
+
+
+ {selected.role}
+
-
- {selected.body.map((para) => (
-
{para}
+
+ {selected.messages.map((m) => (
+
+
+ {m.text}
+
+
+ {m.time}
+
+
))}
-
+
+
) : (
@@ -212,9 +301,9 @@ export function MessagesView() {
-
No message selected
+
No conversation selected
- Choose a message from the inbox to read it here.
+ Choose a conversation from the inbox to read and reply.
diff --git a/frontend/components/prescriptions/add-prescription-dialog.tsx b/frontend/components/prescriptions/add-prescription-dialog.tsx
index eab30b9..ed8dadd 100644
--- a/frontend/components/prescriptions/add-prescription-dialog.tsx
+++ b/frontend/components/prescriptions/add-prescription-dialog.tsx
@@ -1,8 +1,9 @@
"use client";
-import { Search } from "lucide-react";
+import { AlertTriangle, Search } from "lucide-react";
import { type FormEvent, type ReactNode, useEffect, useMemo, useState } from "react";
+import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -39,6 +40,50 @@ const FREQUENCIES = [
const controlClass =
"h-9 w-full rounded-3xl border border-transparent bg-input/50 px-3 text-sm text-foreground outline-none transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30";
+// Mock pharmacology: pairs of drug keywords known to interact. Checked against
+// the patient's current medications. Not clinical advice — illustrative only.
+const INTERACTIONS: [string, string][] = [
+ ["warfarin", "aspirin"],
+ ["warfarin", "ibuprofen"],
+ ["lisinopril", "potassium"],
+ ["lisinopril", "spironolactone"],
+ ["simvastatin", "clarithromycin"],
+ ["metformin", "contrast"],
+ ["amoxicillin", "methotrexate"],
+];
+
+const has = (haystack: string, needle: string) =>
+ haystack.toLowerCase().includes(needle.toLowerCase());
+
+// Find interaction/allergy conflicts between a new medication and the patient's
+// existing record. Returns human-readable warning lines.
+function findConflicts(medication: string, patient: Patient): string[] {
+ const med = medication.trim();
+ if (med.length < 3) return [];
+ const conflicts: string[] = [];
+
+ for (const allergy of patient.allergies) {
+ if (has(med, allergy.substance) || has(allergy.substance, med)) {
+ conflicts.push(
+ `Allergy: patient is allergic to ${allergy.substance} (${allergy.reaction}).`,
+ );
+ }
+ }
+
+ for (const current of patient.medications) {
+ for (const [a, b] of INTERACTIONS) {
+ const hit =
+ (has(med, a) && has(current.name, b)) ||
+ (has(med, b) && has(current.name, a));
+ if (hit) {
+ conflicts.push(`May interact with ${current.name} (current medication).`);
+ }
+ }
+ }
+
+ return [...new Set(conflicts)];
+}
+
function Field({ label, children }: { label: string; children: ReactNode }) {
return (