diff --git a/frontend/components/invoices/invoice-form-dialog.tsx b/frontend/components/invoices/invoice-form-dialog.tsx index 77c4d10..68b2d64 100644 --- a/frontend/components/invoices/invoice-form-dialog.tsx +++ b/frontend/components/invoices/invoice-form-dialog.tsx @@ -12,6 +12,7 @@ import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; import { Calendar } from "@/components/ui/calendar"; +import { Checkbox } from "@/components/ui/checkbox"; import { Combobox, type ComboboxOption } from "@/components/ui/combobox"; import { Dialog, @@ -367,11 +368,14 @@ export function InvoiceFormDialog({
{t("invoices.dialog.issued")} - @@ -385,11 +389,10 @@ export function InvoiceFormDialog({
{t("invoices.dialog.due")} - setHasDue(e.target.checked)} - type="checkbox" + onCheckedChange={(checked) => setHasDue(checked)} /> {hasDue ? ( diff --git a/frontend/components/patients/patient-detail.tsx b/frontend/components/patients/patient-detail.tsx index e3df3e4..2f60754 100644 --- a/frontend/components/patients/patient-detail.tsx +++ b/frontend/components/patients/patient-detail.tsx @@ -7,6 +7,7 @@ import { type LucideIcon, ListTodo, Mic, + MoreHorizontal, Network, NotebookPen, Pencil, @@ -29,6 +30,13 @@ import { import { printPatientSummary } from "@/lib/patient-pdf"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; +import { + Menu, + MenuItem, + MenuPopup, + MenuSeparator, + MenuTrigger, +} from "@/components/ui/menu"; import { Dialog, DialogClose, @@ -315,63 +323,63 @@ export function PatientDetail({ )}
- {/* Actions — their own wrapping row beneath the identity. */} -
- - {onTransfer && ( - - )} - {onScribe && ( - - )} + {/* Actions — one primary (Edit) with the rest tucked into an overflow + menu so the header stays uncluttered. */} +
{onEdit && ( )} - {onWalletPush && ( - - )} - {onDelete && ( - - )} + + + + printPatientSummary(patient, t)}> + + {t("patientCard.exportPdf")} + + {onScribe && ( + + + {t("scribe.recordVisit")} + + )} + {onTransfer && ( + + + {t("patients.transfer.action")} + + )} + {onWalletPush && ( + + + {t("walletPush.action")} + + )} + {onDelete && ( + <> + + + + {t("patients.delete.action")} + + + )} + +
diff --git a/frontend/components/patients/patients-view.tsx b/frontend/components/patients/patients-view.tsx index 35230bd..a053664 100644 --- a/frontend/components/patients/patients-view.tsx +++ b/frontend/components/patients/patients-view.tsx @@ -1,6 +1,6 @@ "use client"; -import { Plus, Search, Smartphone } from "lucide-react"; +import { MoreHorizontal, Plus, Search, Smartphone } from "lucide-react"; import { useSearchParams } from "next/navigation"; import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -11,8 +11,15 @@ import { ImportFromWalletDialog } from "@/components/patients/import-from-wallet import { PatientDetailSheet } from "@/components/patients/patient-detail-sheet"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; +import { CardFrame } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { ListPagination } from "@/components/ui/list-pagination"; +import { + Menu, + MenuItem, + MenuPopup, + MenuTrigger, +} from "@/components/ui/menu"; import { Select, SelectItem, @@ -20,6 +27,14 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; import { listPatients, type Patient } from "@/lib/patients"; type StatusFilter = "all" | Patient["status"]; @@ -181,15 +196,6 @@ export function PatientsView() { ))} - + {/* Secondary actions tuck into an overflow menu so the toolbar keeps a + single primary CTA. */} + + + } + > + + + + setImportOpen(true)}> + + {t("patients.importFromApp")} + + + -
- - - - - -
{t("patients.columns.name")}{t("patients.columns.mrn")} + + + + + + {t("patients.columns.name")} + + + {t("patients.columns.mrn")} + + {t("patients.columns.ageSex")} - - - - - - - + + + + {loading ? ( - - - + + ) : loadError ? ( - - - + + ) : patients.length === 0 ? ( - - - + + ) : ( pageRows.map((p) => ( - open(p.fileNumber)} onKeyDown={(event) => { @@ -264,7 +300,7 @@ export function PatientsView() { role="button" tabIndex={0} > - - - - - - - + + )) )} - -
+ + {t("patients.columns.status")} - + + {t("patients.columns.lastSeen")} - + + {t("patients.columns.allergies")} -
+ {t("patients.loading")} -
+ + {loadError} -
+ {t("patients.empty")} -
+ {p.name} @@ -274,30 +310,30 @@ export function PatientsView() { ) : null} - + + {p.fileNumber} - + + {p.age} · {p.sex} - + + {t(`patients.status.${p.status}`)} - + + {p.encounters[0]?.date ?? "—"} - + + {p.allergies.length || "—"} -
- + +
+ {!loading && !loadError ? ( + , + state: CheckboxPrimitive.Indicator.State, + ) => ( + + {state.indeterminate ? ( + + ) : ( + + )} + + )} + /> + + ); +} + +export { CheckboxPrimitive }; diff --git a/frontend/components/ui/table.tsx b/frontend/components/ui/table.tsx new file mode 100644 index 0000000..6af027a --- /dev/null +++ b/frontend/components/ui/table.tsx @@ -0,0 +1,151 @@ +"use client"; + +import { mergeProps } from "@base-ui/react/merge-props"; +import { useRender } from "@base-ui/react/use-render"; +import type React from "react"; +import { cn } from "@/lib/utils"; + +export type TableVariant = "default" | "card"; + +export type TableProps = React.ComponentProps<"table"> & { + variant?: TableVariant; + render?: useRender.ComponentProps<"div">["render"]; +}; + +export function Table({ + className, + variant = "default", + render, + ...props +}: TableProps): React.ReactElement { + const defaultProps = { + children: ( + + ), + className: "relative w-full overflow-x-auto", + "data-slot": "table-container", + "data-variant": variant, + }; + + return useRender({ + defaultTagName: "div", + props: mergeProps<"div">(defaultProps, {}), + render, + }); +} + +export function TableHeader({ + className, + ...props +}: React.ComponentProps<"thead">): React.ReactElement { + return ( + + ); +} + +export function TableBody({ + className, + ...props +}: React.ComponentProps<"tbody">): React.ReactElement { + return ( + + ); +} + +export function TableFooter({ + className, + ...props +}: React.ComponentProps<"tfoot">): React.ReactElement { + return ( + tr]:last:border-b-0", + className, + )} + data-slot="table-footer" + {...props} + /> + ); +} + +export function TableRow({ + className, + ...props +}: React.ComponentProps<"tr">): React.ReactElement { + return ( + + ); +} + +export function TableHead({ + className, + ...props +}: React.ComponentProps<"th">): React.ReactElement { + return ( +
+ ); +} + +export function TableCell({ + className, + ...props +}: React.ComponentProps<"td">): React.ReactElement { + return ( + + ); +} + +export function TableCaption({ + className, + ...props +}: React.ComponentProps<"caption">): React.ReactElement { + return ( +
+ ); +} diff --git a/frontend/lib/i18n/locales/ar/translation.json b/frontend/lib/i18n/locales/ar/translation.json index e324dd8..be9c3f6 100644 --- a/frontend/lib/i18n/locales/ar/translation.json +++ b/frontend/lib/i18n/locales/ar/translation.json @@ -377,7 +377,8 @@ "failedBody": "يرجى المحاولة مرة أخرى." }, "filterStatus": "تصفية حسب الحالة", - "allStatuses": "كل الحالات" + "allStatuses": "كل الحالات", + "moreActions": "إجراءات إضافية" }, "appointments": { "title": "المواعيد والجدول", @@ -1555,7 +1556,8 @@ "mild": "خفيف", "moderate": "متوسط", "severe": "شديد" - } + }, + "moreActions": "إجراءات إضافية" }, "patientForm": { "editTitle": "تعديل السجل", diff --git a/frontend/lib/i18n/locales/de/translation.json b/frontend/lib/i18n/locales/de/translation.json index 3500595..b303722 100644 --- a/frontend/lib/i18n/locales/de/translation.json +++ b/frontend/lib/i18n/locales/de/translation.json @@ -365,7 +365,8 @@ "failedBody": "Bitte versuchen Sie es erneut." }, "filterStatus": "Nach Status filtern", - "allStatuses": "Alle Status" + "allStatuses": "Alle Status", + "moreActions": "Weitere Aktionen" }, "appointments": { "title": "Termine & Zeitplan", @@ -1535,7 +1536,8 @@ "mild": "Leicht", "moderate": "Mäßig", "severe": "Schwer" - } + }, + "moreActions": "Weitere Aktionen" }, "patientForm": { "editTitle": "Datensatz bearbeiten", diff --git a/frontend/lib/i18n/locales/en/translation.json b/frontend/lib/i18n/locales/en/translation.json index 620a6ad..7e81005 100644 --- a/frontend/lib/i18n/locales/en/translation.json +++ b/frontend/lib/i18n/locales/en/translation.json @@ -365,7 +365,8 @@ "failedBody": "Please try again." }, "filterStatus": "Filter by status", - "allStatuses": "All statuses" + "allStatuses": "All statuses", + "moreActions": "More actions" }, "appointments": { "title": "Appointments & Schedule", @@ -1535,7 +1536,8 @@ "mild": "Mild", "moderate": "Moderate", "severe": "Severe" - } + }, + "moreActions": "More actions" }, "patientForm": { "editTitle": "Edit record", diff --git a/frontend/lib/i18n/locales/fr/translation.json b/frontend/lib/i18n/locales/fr/translation.json index c239986..4a96e3a 100644 --- a/frontend/lib/i18n/locales/fr/translation.json +++ b/frontend/lib/i18n/locales/fr/translation.json @@ -365,7 +365,8 @@ "failedBody": "Veuillez réessayer." }, "filterStatus": "Filtrer par statut", - "allStatuses": "Tous les statuts" + "allStatuses": "Tous les statuts", + "moreActions": "Plus d’actions" }, "appointments": { "title": "Rendez-vous & Planning", @@ -1535,7 +1536,8 @@ "mild": "Légère", "moderate": "Modérée", "severe": "Sévère" - } + }, + "moreActions": "Plus d’actions" }, "patientForm": { "editTitle": "Modifier le dossier", diff --git a/frontend/lib/i18n/locales/so/translation.json b/frontend/lib/i18n/locales/so/translation.json index 7f92b28..c5af05e 100644 --- a/frontend/lib/i18n/locales/so/translation.json +++ b/frontend/lib/i18n/locales/so/translation.json @@ -365,7 +365,8 @@ "failedBody": "Fadlan isku day mar kale." }, "filterStatus": "Ku kala sooc xaaladda", - "allStatuses": "Dhammaan xaaladaha" + "allStatuses": "Dhammaan xaaladaha", + "moreActions": "Ficillo dheeraad ah" }, "appointments": { "title": "Ballamaha & Jadwalka", @@ -1535,7 +1536,8 @@ "mild": "Fudud", "moderate": "Dhexdhexaad", "severe": "Daran" - } + }, + "moreActions": "Ficillo dheeraad ah" }, "patientForm": { "editTitle": "Wax ka beddel diiwaanka",