From 2c239fbd2777cd1344c4e75fff5a3cb36867d00a Mon Sep 17 00:00:00 2001 From: Khalid Abdi Date: Sun, 12 Jul 2026 21:06:38 +0300 Subject: [PATCH] frontend: UI fixes + settings/patients features - Record history: replace fragile COSS Timeline separator math with a continuous-rail list so every audited change renders in full (RTL-safe). - Prescriptions: new reusable COSS DatePicker (Popover + Calendar) replaces raw for Start/End date. - Update banner: rebuilt with COSS Alert variant="warning"; pinned physical bottom-right so it stays bottom-right under Arabic RTL. - AI setup notice: thin attached warning banner with clearer copy; now shown above the input in active chats too, not just the empty state. - Settings profile: wire the previously dead Specialty (Select) and Professional links (editable rows) into the persisted preferences. - Patients: add a status filter and broaden search to conditions/allergies. - Sidebar user menu: quick language switch submenu (RTL already wired). - ai-elements: fix a subset of Base UI type drift (22 -> 12; remainder is cmdk->Autocomplete migration drift kept behind ignoreBuildErrors). - i18n: new keys added across all five locales (en/de/fr/ar/so). Co-Authored-By: Claude Opus 4.8 --- .../components/ai-elements/attachments.tsx | 15 ++- frontend/components/ai-elements/context.tsx | 2 +- .../ai-elements/inline-citation.tsx | 2 +- frontend/components/ai-elements/plan.tsx | 15 ++- .../components/ai-elements/prompt-input.tsx | 15 ++- .../components/ai-elements/schema-display.tsx | 4 +- frontend/components/chat/ai-setup-notice.tsx | 44 ++++---- frontend/components/chat/chat-panel.tsx | 6 +- .../components/patients/patient-detail.tsx | 61 +++++------ .../components/patients/patients-view.tsx | 54 ++++++++- .../prescriptions/add-prescription-dialog.tsx | 15 ++- .../settings/settings-preferences.tsx | 96 ++++++++++++++-- frontend/components/sidebar-02/nav-user.tsx | 34 +++++- frontend/components/ui/date-picker.tsx | 103 ++++++++++++++++++ frontend/components/update-banner.tsx | 52 +++++---- frontend/lib/i18n/locales/ar/translation.json | 14 ++- frontend/lib/i18n/locales/de/translation.json | 14 ++- frontend/lib/i18n/locales/en/translation.json | 14 ++- frontend/lib/i18n/locales/fr/translation.json | 14 ++- frontend/lib/i18n/locales/so/translation.json | 14 ++- 20 files changed, 455 insertions(+), 133 deletions(-) create mode 100644 frontend/components/ui/date-picker.tsx diff --git a/frontend/components/ai-elements/attachments.tsx b/frontend/components/ai-elements/attachments.tsx index 6a3c036..3876e1f 100644 --- a/frontend/components/ai-elements/attachments.tsx +++ b/frontend/components/ai-elements/attachments.tsx @@ -369,15 +369,18 @@ export const AttachmentRemove = ({ // AttachmentHoverCard - Hover preview // ============================================================================ -export type AttachmentHoverCardProps = ComponentProps; +export type AttachmentHoverCardProps = ComponentProps & { + // Base UI moved hover delay to the Trigger (`delay`); kept here for API + // back-compat but no longer forwarded to the Root. + openDelay?: number; + closeDelay?: number; +}; export const AttachmentHoverCard = ({ - openDelay = 0, - closeDelay = 0, + openDelay, + closeDelay, ...props -}: AttachmentHoverCardProps) => ( - -); +}: AttachmentHoverCardProps) => ; export type AttachmentHoverCardTriggerProps = ComponentProps< typeof HoverCardTrigger diff --git a/frontend/components/ai-elements/context.tsx b/frontend/components/ai-elements/context.tsx index c4fe7b6..45cbb0c 100644 --- a/frontend/components/ai-elements/context.tsx +++ b/frontend/components/ai-elements/context.tsx @@ -56,7 +56,7 @@ export const Context = ({ return ( - + ); }; diff --git a/frontend/components/ai-elements/inline-citation.tsx b/frontend/components/ai-elements/inline-citation.tsx index 8194d38..70707d4 100644 --- a/frontend/components/ai-elements/inline-citation.tsx +++ b/frontend/components/ai-elements/inline-citation.tsx @@ -50,7 +50,7 @@ export const InlineCitationText = ({ export type InlineCitationCardProps = ComponentProps; export const InlineCitationCard = (props: InlineCitationCardProps) => ( - + ); export type InlineCitationCardTriggerProps = ComponentProps & { diff --git a/frontend/components/ai-elements/plan.tsx b/frontend/components/ai-elements/plan.tsx index 67d9e79..bdb256d 100644 --- a/frontend/components/ai-elements/plan.tsx +++ b/frontend/components/ai-elements/plan.tsx @@ -128,5 +128,18 @@ export const PlanFooter = (props: PlanFooterProps) => ( export type PlanTriggerProps = ComponentProps; export const PlanTrigger = ({ className, ...props }: PlanTriggerProps) => ( - }>Toggle plan + + } + > + + Toggle plan + ); diff --git a/frontend/components/ai-elements/prompt-input.tsx b/frontend/components/ai-elements/prompt-input.tsx index 5c3fe8b..0eca87d 100644 --- a/frontend/components/ai-elements/prompt-input.tsx +++ b/frontend/components/ai-elements/prompt-input.tsx @@ -1311,15 +1311,18 @@ export const PromptInputSelectValue = ({ ); -export type PromptInputHoverCardProps = ComponentProps; +export type PromptInputHoverCardProps = ComponentProps & { + // Base UI moved hover delay to the Trigger (`delay`); kept here for API + // back-compat but no longer forwarded to the Root. + openDelay?: number; + closeDelay?: number; +}; export const PromptInputHoverCard = ({ - openDelay = 0, - closeDelay = 0, + openDelay, + closeDelay, ...props -}: PromptInputHoverCardProps) => ( - -); +}: PromptInputHoverCardProps) => ; export type PromptInputHoverCardTriggerProps = ComponentProps< typeof HoverCardTrigger diff --git a/frontend/components/ai-elements/schema-display.tsx b/frontend/components/ai-elements/schema-display.tsx index 24f7b92..ae13246 100644 --- a/frontend/components/ai-elements/schema-display.tsx +++ b/frontend/components/ai-elements/schema-display.tsx @@ -107,7 +107,9 @@ export const SchemaDisplayPath = ({ ); diff --git a/frontend/components/chat/ai-setup-notice.tsx b/frontend/components/chat/ai-setup-notice.tsx index bac0149..a4e7d41 100644 --- a/frontend/components/chat/ai-setup-notice.tsx +++ b/frontend/components/chat/ai-setup-notice.tsx @@ -1,6 +1,6 @@ "use client"; -import { Sparkles, X } from "lucide-react"; +import { TriangleAlert, X } from "lucide-react"; import Link from "next/link"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -8,9 +8,12 @@ import { useTranslation } from "react-i18next"; import { Button } from "@/components/ui/button"; import { getAiConfig } from "@/lib/ai-settings"; -// A single, dismissible heads-up shown above the chat input on a fresh chat when -// no AI provider is configured yet (no API key, and no local Ollama). It only -// renders on the empty state, so it naturally disappears once a message is sent. +// A thin, dismissible warning bar shown flush above the chat input whenever no +// AI provider is wired up yet — either no API key for any cloud provider, or the +// app is in local mode with no Ollama endpoint set. Without this, sending a +// message just fails silently, so the banner spells out the missing setup and +// links straight to AI settings. Rendered in both the empty and active chat +// states so a user mid-conversation with no provider still sees why replies fail. export function AiSetupNotice() { const { t } = useTranslation(); const [needsSetup, setNeedsSetup] = useState(false); @@ -40,24 +43,25 @@ export function AiSetupNotice() { return (
- -
-

- {t("chat.setupNotice.title")} -

-

{t("chat.setupNotice.body")}

- -
+ +

+ {t("chat.setupNotice.title")} + + {" — "} + {t("chat.setupNotice.body")} + +

+
diff --git a/frontend/components/patients/patient-detail.tsx b/frontend/components/patients/patient-detail.tsx index b41cfd4..e3df3e4 100644 --- a/frontend/components/patients/patient-detail.tsx +++ b/frontend/components/patients/patient-detail.tsx @@ -21,16 +21,6 @@ import { useTranslation } from "react-i18next"; import { Sparkline } from "@/components/chat/sparkline"; import { AttachmentsSection } from "@/components/patients/patient-files"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; -import { - Timeline, - TimelineContent, - TimelineDate, - TimelineHeader, - TimelineIndicator, - TimelineItem, - TimelineSeparator, - TimelineTitle, -} from "@/components/ui/timeline"; import { type ActivityEntityType, type ActivityEntry, @@ -182,36 +172,41 @@ function RecordHistory({ fileNumber }: { fileNumber: string }) { {t("patientCard.history.empty")}

) : ( - // Every entry is a past, audited event, so mark them all completed - // (filled indicators) by seeding the active step past the last item. - + // A plain vertical rail so EVERY audited change renders in full — the + // icon column draws a connector that stretches to the next entry, and + // the flex layout mirrors correctly under RTL. +
    {entries.map((e, i) => { const Icon = historyIcon[e.entityType] ?? Pencil; + const isLast = i === entries.length - 1; return ( - - - - - {e.actorName} - - +
  1. +
    + - - - - {e.action} - + + {!isLast && ( +
    +
    +

    + {e.actorName} +

    +

    {e.action}

    + +
    +
  2. ); })} - +
)} ); diff --git a/frontend/components/patients/patients-view.tsx b/frontend/components/patients/patients-view.tsx index 0ee517b..35230bd 100644 --- a/frontend/components/patients/patients-view.tsx +++ b/frontend/components/patients/patients-view.tsx @@ -13,8 +13,23 @@ import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { ListPagination } from "@/components/ui/list-pagination"; +import { + Select, + SelectItem, + SelectPopup, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { listPatients, type Patient } from "@/lib/patients"; +type StatusFilter = "all" | Patient["status"]; +const STATUS_FILTERS: StatusFilter[] = [ + "all", + "active", + "inpatient", + "discharged", +]; + // Rows shown per page on the patients table before paginating. const PAGE_SIZE = 10; @@ -32,6 +47,7 @@ const statusVariant: Record = { export function PatientsView() { const { t } = useTranslation(); const [query, setQuery] = useState(""); + const [statusFilter, setStatusFilter] = useState("all"); const [addOpen, setAddOpen] = useState(false); const [importOpen, setImportOpen] = useState(false); // Bumped on open so the create dialog remounts with a fresh file # / form. @@ -69,9 +85,18 @@ export function PatientsView() { }, []); const q = query.trim().toLowerCase(); - const patients = allPatients.filter( - (p) => !q || p.name.toLowerCase().includes(q) || p.fileNumber.includes(q) - ); + // Search matches name, MRN, problem/condition labels, and allergy substances; + // the status filter narrows to one clinical status. + const patients = allPatients.filter((p) => { + if (statusFilter !== "all" && p.status !== statusFilter) return false; + if (!q) return true; + return ( + p.name.toLowerCase().includes(q) || + p.fileNumber.includes(q) || + p.problems.some((problem) => problem.label.toLowerCase().includes(q)) || + p.allergies.some((a) => a.substance.toLowerCase().includes(q)) + ); + }); // Client-side pagination over the filtered list (10/page). Searching resets to // the first page (done in the search handler); `page` is clamped at render so a @@ -133,6 +158,29 @@ export function PatientsView() { value={query} /> + + + + {(value: string) => + value ? ( + specialtyLabel(t, value) + ) : ( + + {t("settings.profile.selectSpecialty")} + + ) + } + + + + {SPECIALTIES.map((s) => ( + + {t(`settings.careTeam.specialties.${s}`)} + + ))} + +
@@ -221,7 +263,43 @@ export function ProfilePanel() { {t("settings.profile.professionalLinksHint")}

- + + ))} + + ) : null} + diff --git a/frontend/components/sidebar-02/nav-user.tsx b/frontend/components/sidebar-02/nav-user.tsx index a866171..4e5698f 100644 --- a/frontend/components/sidebar-02/nav-user.tsx +++ b/frontend/components/sidebar-02/nav-user.tsx @@ -4,6 +4,7 @@ import { Building2, Check, ChevronsUpDown, + Languages, LogOut, Moon, Plus, @@ -49,6 +50,8 @@ import { useSidebar, } from "@/components/ui/sidebar"; import { authClient } from "@/lib/auth-client"; +import { supportedLanguages } from "@/lib/i18n/config"; +import { persistLanguage } from "@/lib/language"; import { useActiveRole } from "@/lib/roles"; import { notify } from "@/lib/toast"; @@ -82,7 +85,13 @@ function GitHubIcon({ className }: { className?: string }) { // shortcut hint (below Theme) and a clinic switcher submenu (below that) whose // popup reveals the active clinic's details on hover. export function NavUser() { - const { t } = useTranslation(); + const { t, i18n } = useTranslation(); + const activeLang = i18n.resolvedLanguage ?? i18n.language; + const changeLanguage = (lng: string) => { + if (lng === activeLang) return; + void i18n.changeLanguage(lng); + void persistLanguage(lng); + }; const { isMobile, state } = useSidebar(); const isCollapsed = state === "collapsed"; const router = useRouter(); @@ -197,6 +206,29 @@ export function NavUser() { + {/* Language: quick switch (also available in Settings). Applies + immediately and roams via the backend preferences. */} + + + + {t("userMenu.language")} + + + {supportedLanguages.map((lng) => ( + changeLanguage(lng)} + > + + {t(`settings.profile.language.${lng}`)} + + {lng === activeLang && } + + ))} + + + {/* Command palette: hint + shortcut, sits below Theme. */} diff --git a/frontend/components/ui/date-picker.tsx b/frontend/components/ui/date-picker.tsx new file mode 100644 index 0000000..2dbcb0d --- /dev/null +++ b/frontend/components/ui/date-picker.tsx @@ -0,0 +1,103 @@ +"use client"; + +import { CalendarIcon } from "lucide-react"; +import type { Matcher } from "react-day-picker"; +import { useState } from "react"; + +import { Button } from "@/components/ui/button"; +import { Calendar } from "@/components/ui/calendar"; +import { + Popover, + PopoverPopup, + PopoverTrigger, +} from "@/components/ui/popover"; +import { cn } from "@/lib/utils"; + +// Parse a `YYYY-MM-DD` string as a LOCAL date. `new Date("YYYY-MM-DD")` parses +// as UTC and can shift the day across timezones, so build it from parts. +function parseISODate(value?: string): Date | undefined { + if (!value) return undefined; + const [y, m, d] = value.split("-").map(Number); + if (!y || !m || !d) return undefined; + return new Date(y, m - 1, d); +} + +function toISODate(date: Date): string { + const y = date.getFullYear(); + const m = String(date.getMonth() + 1).padStart(2, "0"); + const d = String(date.getDate()).padStart(2, "0"); + return `${y}-${m}-${d}`; +} + +export interface DatePickerProps { + /** Selected date as `YYYY-MM-DD` (matches native date-input semantics). */ + value?: string; + onChange: (value: string) => void; + /** Earliest selectable date, `YYYY-MM-DD`. */ + min?: string; + /** Latest selectable date, `YYYY-MM-DD`. */ + max?: string; + placeholder?: string; + id?: string; + className?: string; + disabled?: boolean; +} + +// A COSS date picker: an outline Button trigger opening a Popover with the COSS +// Calendar. Drop-in replacement for ``, keeping the same +// string value shape so callers don't change their state handling. +export function DatePicker({ + value, + onChange, + min, + max, + placeholder, + id, + className, + disabled, +}: DatePickerProps): React.ReactElement { + const [open, setOpen] = useState(false); + const selected = parseISODate(value); + const minDate = parseISODate(min); + const maxDate = parseISODate(max); + + const disabledMatchers: Matcher[] = [ + ...(minDate ? [{ before: minDate }] : []), + ...(maxDate ? [{ after: maxDate }] : []), + ]; + + return ( + + + } + > + {selected ? selected.toLocaleDateString() : (placeholder ?? "")} + + + + { + onChange(date ? toISODate(date) : ""); + if (date) setOpen(false); + }} + selected={selected} + /> + + + ); +} diff --git a/frontend/components/update-banner.tsx b/frontend/components/update-banner.tsx index bc4b63a..8bec1e6 100644 --- a/frontend/components/update-banner.tsx +++ b/frontend/components/update-banner.tsx @@ -1,10 +1,12 @@ "use client"; import Link from "next/link"; -import { X } from "lucide-react"; +import { ArrowUpCircle, X } from "lucide-react"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; +import { Alert, AlertAction, AlertTitle } from "@/components/ui/alert"; +import { Button } from "@/components/ui/button"; import { getVersionInfo } from "@/lib/version"; const DISMISS_KEY = "temetro:update-dismissed"; @@ -39,28 +41,36 @@ export function UpdateBanner() { setLatest(null); }; + // Pinned to the physical bottom-right in both LTR and RTL. The user wants it + // in the bottom-right corner in Arabic too, so we use physical `right`/`bottom` + // rather than the logical `end` (which would flip to the left under RTL). return ( -
-
-

+

+ + + {t("settings.version.banner", { version: latest })} -

- - {t("settings.version.bannerUpdate")} - -
- + + + + + +
); } diff --git a/frontend/lib/i18n/locales/ar/translation.json b/frontend/lib/i18n/locales/ar/translation.json index 8121610..e324dd8 100644 --- a/frontend/lib/i18n/locales/ar/translation.json +++ b/frontend/lib/i18n/locales/ar/translation.json @@ -263,7 +263,8 @@ "createDialogDescription": "أضف عيادة جديدة وانتقل إليها.", "signOutFailed": "فشل تسجيل الخروج", "tryAgain": "يرجى المحاولة مرة أخرى.", - "signedOut": "تم تسجيل الخروج" + "signedOut": "تم تسجيل الخروج", + "language": "اللغة" }, "clinic": { "create": "إنشاء عيادة", @@ -374,7 +375,9 @@ "doneTitle": "تم حذف المريض", "failedTitle": "تعذّر حذف المريض", "failedBody": "يرجى المحاولة مرة أخرى." - } + }, + "filterStatus": "تصفية حسب الحالة", + "allStatuses": "كل الحالات" }, "appointments": { "title": "المواعيد والجدول", @@ -615,7 +618,8 @@ "removeDates": "إزالة", "startDate": "تاريخ البدء", "endDate": "تاريخ الانتهاء", - "addDates": "إضافة تاريخ البدء/الانتهاء" + "addDates": "إضافة تاريخ البدء/الانتهاء", + "selectDate": "اختر التاريخ" } }, "pharmacy": { @@ -1886,7 +1890,9 @@ "pendingApprovalsDesc": "أبلغني عند موافقة مريض على تغيير معلّق أو رفضه", "recordsShared": "سجلات شُوركت معي", "recordsSharedDesc": "أبلغني عند مشاركة مريض سجلًا معي" - } + }, + "linkPlaceholder": "https://example.com", + "removeLink": "إزالة الرابط" }, "careTeam": { "title": "فريق الرعاية", diff --git a/frontend/lib/i18n/locales/de/translation.json b/frontend/lib/i18n/locales/de/translation.json index e92defc..3500595 100644 --- a/frontend/lib/i18n/locales/de/translation.json +++ b/frontend/lib/i18n/locales/de/translation.json @@ -259,7 +259,8 @@ "createDialogDescription": "Fügen Sie eine neue Klinik hinzu und wechseln Sie zu ihr.", "signOutFailed": "Abmeldung fehlgeschlagen", "tryAgain": "Bitte versuchen Sie es erneut.", - "signedOut": "Abgemeldet" + "signedOut": "Abgemeldet", + "language": "Sprache" }, "clinic": { "create": "Klinik erstellen", @@ -362,7 +363,9 @@ "doneTitle": "Patient gelöscht", "failedTitle": "Patient konnte nicht gelöscht werden", "failedBody": "Bitte versuchen Sie es erneut." - } + }, + "filterStatus": "Nach Status filtern", + "allStatuses": "Alle Status" }, "appointments": { "title": "Termine & Zeitplan", @@ -599,7 +602,8 @@ "removeDates": "Entfernen", "startDate": "Startdatum", "endDate": "Enddatum", - "addDates": "Start-/Enddatum hinzufügen" + "addDates": "Start-/Enddatum hinzufügen", + "selectDate": "Datum wählen" } }, "pharmacy": { @@ -1866,7 +1870,9 @@ "pendingApprovalsDesc": "Benachrichtigen Sie mich, wenn ein Patient eine ausstehende Änderung genehmigt oder ablehnt", "recordsShared": "Mit mir geteilte Datensätze", "recordsSharedDesc": "Benachrichtigen Sie mich, wenn ein Patient einen Datensatz mit mir teilt" - } + }, + "linkPlaceholder": "https://example.com", + "removeLink": "Link entfernen" }, "careTeam": { "title": "Behandlungsteam", diff --git a/frontend/lib/i18n/locales/en/translation.json b/frontend/lib/i18n/locales/en/translation.json index 92be106..620a6ad 100644 --- a/frontend/lib/i18n/locales/en/translation.json +++ b/frontend/lib/i18n/locales/en/translation.json @@ -259,7 +259,8 @@ "createDialogDescription": "Add a new clinic and switch to it.", "signOutFailed": "Sign out failed", "tryAgain": "Please try again.", - "signedOut": "Signed out" + "signedOut": "Signed out", + "language": "Language" }, "clinic": { "create": "Create clinic", @@ -362,7 +363,9 @@ "doneTitle": "Patient deleted", "failedTitle": "Couldn't delete patient", "failedBody": "Please try again." - } + }, + "filterStatus": "Filter by status", + "allStatuses": "All statuses" }, "appointments": { "title": "Appointments & Schedule", @@ -599,7 +602,8 @@ "removeDates": "Remove", "startDate": "Start date", "endDate": "End date", - "addDates": "Add start/end dates" + "addDates": "Add start/end dates", + "selectDate": "Select date" } }, "pharmacy": { @@ -1866,7 +1870,9 @@ "pendingApprovalsDesc": "Notify me when a patient approves or rejects a pending change", "recordsShared": "Records shared with me", "recordsSharedDesc": "Notify me when a patient shares a record with me" - } + }, + "linkPlaceholder": "https://example.com", + "removeLink": "Remove link" }, "careTeam": { "title": "Care team", diff --git a/frontend/lib/i18n/locales/fr/translation.json b/frontend/lib/i18n/locales/fr/translation.json index abb2d70..c239986 100644 --- a/frontend/lib/i18n/locales/fr/translation.json +++ b/frontend/lib/i18n/locales/fr/translation.json @@ -259,7 +259,8 @@ "createDialogDescription": "Ajoutez une nouvelle clinique et passez-y.", "signOutFailed": "Échec de la déconnexion", "tryAgain": "Veuillez réessayer.", - "signedOut": "Déconnecté" + "signedOut": "Déconnecté", + "language": "Langue" }, "clinic": { "create": "Créer une clinique", @@ -362,7 +363,9 @@ "doneTitle": "Patient supprimé", "failedTitle": "Impossible de supprimer le patient", "failedBody": "Veuillez réessayer." - } + }, + "filterStatus": "Filtrer par statut", + "allStatuses": "Tous les statuts" }, "appointments": { "title": "Rendez-vous & Planning", @@ -599,7 +602,8 @@ "removeDates": "Retirer", "startDate": "Date de début", "endDate": "Date de fin", - "addDates": "Ajouter les dates de début/fin" + "addDates": "Ajouter les dates de début/fin", + "selectDate": "Choisir une date" } }, "pharmacy": { @@ -1866,7 +1870,9 @@ "pendingApprovalsDesc": "Me notifier lorsqu'un patient approuve ou rejette une modification en attente", "recordsShared": "Dossiers partagés avec moi", "recordsSharedDesc": "Me notifier lorsqu'un patient partage un dossier avec moi" - } + }, + "linkPlaceholder": "https://example.com", + "removeLink": "Supprimer le lien" }, "careTeam": { "title": "Équipe soignante", diff --git a/frontend/lib/i18n/locales/so/translation.json b/frontend/lib/i18n/locales/so/translation.json index 2e21e8a..7f92b28 100644 --- a/frontend/lib/i18n/locales/so/translation.json +++ b/frontend/lib/i18n/locales/so/translation.json @@ -259,7 +259,8 @@ "createDialogDescription": "Ku dar rug cusub oo u beddel.", "signOutFailed": "Ka-bixitaanka waa fashilmay", "tryAgain": "Fadlan isku day mar kale.", - "signedOut": "Waa laga baxay" + "signedOut": "Waa laga baxay", + "language": "Luqadda" }, "clinic": { "create": "Samee rug", @@ -362,7 +363,9 @@ "doneTitle": "Bukaanka waa la tirtiray", "failedTitle": "Bukaanka lama tirtiri karin", "failedBody": "Fadlan isku day mar kale." - } + }, + "filterStatus": "Ku kala sooc xaaladda", + "allStatuses": "Dhammaan xaaladaha" }, "appointments": { "title": "Ballamaha & Jadwalka", @@ -599,7 +602,8 @@ "removeDates": "Ka saar", "startDate": "Taariikhda bilowga", "endDate": "Taariikhda dhammaadka", - "addDates": "Ku dar taariikhaha bilow/dhammaad" + "addDates": "Ku dar taariikhaha bilow/dhammaad", + "selectDate": "Dooro taariikhda" } }, "pharmacy": { @@ -1866,7 +1870,9 @@ "pendingApprovalsDesc": "I ogeysii marka bukaan ansixiyo ama diido isbeddel la sugayo", "recordsShared": "Diiwaanno la ila wadaagay", "recordsSharedDesc": "I ogeysii marka bukaan diiwaan ila wadaago" - } + }, + "linkPlaceholder": "https://example.com", + "removeLink": "Ka saar linkiga" }, "careTeam": { "title": "Kooxda daryeelka",