mirror of
https://github.com/temetro/temetro.git
synced 2026-08-26 02:17:22 +00:00
frontend: block past dates in appointment/invoice pickers; drop stale Settings Features
- Appointment date picker disables days before today. - Invoice issue-date picker disables past days by default, with an opt-in "Back-date" checkbox for recording older invoices (edit mode keeps past dates). Added invoices.dialog.backdate to all locales. - Removed the inert, outdated "Features" section (patient-owned storage / require signed toggles) from Settings and its now-unused i18n keys. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -51,6 +51,14 @@ const TYPES = [
|
||||
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";
|
||||
|
||||
// Local start-of-day so the calendar can disable days strictly before today
|
||||
// (appointments can't be scheduled in the past).
|
||||
const startOfToday = () => {
|
||||
const d = new Date();
|
||||
d.setHours(0, 0, 0, 0);
|
||||
return d;
|
||||
};
|
||||
|
||||
function Field({ label, children }: { label: string; children: ReactNode }) {
|
||||
return (
|
||||
<label className="flex flex-col gap-1.5">
|
||||
@@ -251,6 +259,7 @@ export function AddAppointmentDialog({
|
||||
/>
|
||||
<PopoverPopup>
|
||||
<Calendar
|
||||
disabled={{ before: startOfToday() }}
|
||||
mode="single"
|
||||
onSelect={(d) => {
|
||||
if (d) {
|
||||
|
||||
@@ -59,12 +59,23 @@ function Field({ label, children }: { label: string; children: ReactNode }) {
|
||||
);
|
||||
}
|
||||
|
||||
// Local start-of-day, used to disable days strictly before today.
|
||||
const startOfToday = () => {
|
||||
const d = new Date();
|
||||
d.setHours(0, 0, 0, 0);
|
||||
return d;
|
||||
};
|
||||
|
||||
function DatePicker({
|
||||
value,
|
||||
onChange,
|
||||
allowPast = true,
|
||||
}: {
|
||||
value: Date;
|
||||
onChange: (d: Date) => void;
|
||||
// When false, days before today are disabled (used for the issue date unless
|
||||
// the clinician opts into back-dating an older invoice).
|
||||
allowPast?: boolean;
|
||||
}) {
|
||||
const [open, setOpen] = useState(false);
|
||||
return (
|
||||
@@ -87,6 +98,7 @@ function DatePicker({
|
||||
/>
|
||||
<PopoverPopup>
|
||||
<Calendar
|
||||
disabled={allowPast ? undefined : { before: startOfToday() }}
|
||||
mode="single"
|
||||
onSelect={(d) => {
|
||||
if (d) {
|
||||
@@ -131,6 +143,9 @@ export function InvoiceFormDialog({
|
||||
: null;
|
||||
|
||||
const [issuedAt, setIssuedAt] = useState<Date>(() => new Date());
|
||||
// Off by default: the issue date can't be back-dated unless the clinician
|
||||
// opts in (for recording an older, pre-existing invoice).
|
||||
const [allowBackdate, setAllowBackdate] = useState(false);
|
||||
const [hasDue, setHasDue] = useState(false);
|
||||
const [dueAt, setDueAt] = useState<Date>(() => new Date());
|
||||
const [status, setStatus] = useState<InvoiceStatus>("draft");
|
||||
@@ -143,6 +158,8 @@ export function InvoiceFormDialog({
|
||||
if (!open) return;
|
||||
if (mode === "edit" && invoice) {
|
||||
setIssuedAt(new Date(`${invoice.issuedAt}T00:00:00`));
|
||||
// Existing invoices legitimately carry past issue dates.
|
||||
setAllowBackdate(true);
|
||||
setHasDue(Boolean(invoice.dueAt));
|
||||
setDueAt(new Date(`${invoice.dueAt ?? invoice.issuedAt}T00:00:00`));
|
||||
setStatus(invoice.status);
|
||||
@@ -153,6 +170,7 @@ export function InvoiceFormDialog({
|
||||
} else {
|
||||
setSelected(null);
|
||||
setIssuedAt(new Date());
|
||||
setAllowBackdate(false);
|
||||
setHasDue(false);
|
||||
setDueAt(new Date());
|
||||
setStatus("draft");
|
||||
@@ -310,9 +328,24 @@ export function InvoiceFormDialog({
|
||||
</Field>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<Field label={t("invoices.dialog.issued")}>
|
||||
<DatePicker onChange={setIssuedAt} value={issuedAt} />
|
||||
</Field>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<span className="flex items-center justify-between gap-2 text-muted-foreground text-xs">
|
||||
{t("invoices.dialog.issued")}
|
||||
<label className="flex items-center gap-1 text-[11px]">
|
||||
<input
|
||||
checked={allowBackdate}
|
||||
onChange={(e) => setAllowBackdate(e.target.checked)}
|
||||
type="checkbox"
|
||||
/>
|
||||
{t("invoices.dialog.backdate")}
|
||||
</label>
|
||||
</span>
|
||||
<DatePicker
|
||||
allowPast={allowBackdate}
|
||||
onChange={setIssuedAt}
|
||||
value={issuedAt}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<span className="flex items-center justify-between text-muted-foreground text-xs">
|
||||
{t("invoices.dialog.due")}
|
||||
|
||||
@@ -293,23 +293,6 @@ export function ProfilePanel() {
|
||||
</div>
|
||||
</SettingsSection>
|
||||
|
||||
{/* Future features — intentionally not wired to persistence yet. */}
|
||||
<SettingsSection
|
||||
description={t("settings.profile.featuresDescription")}
|
||||
title={t("settings.profile.features")}
|
||||
>
|
||||
<div className="space-y-3">
|
||||
<ToggleRow
|
||||
description={t("settings.profile.notif.patientStorageDesc")}
|
||||
title={t("settings.profile.notif.patientStorage")}
|
||||
/>
|
||||
<ToggleRow
|
||||
description={t("settings.profile.notif.requireSignedDesc")}
|
||||
title={t("settings.profile.notif.requireSigned")}
|
||||
/>
|
||||
</div>
|
||||
</SettingsSection>
|
||||
|
||||
<SettingsSection
|
||||
description={t("settings.profile.dangerZoneDescription")}
|
||||
title={t("settings.profile.dangerZone")}
|
||||
|
||||
@@ -482,6 +482,7 @@
|
||||
"fileNumber": "ملف رقم {{number}}",
|
||||
"change": "تغيير",
|
||||
"issued": "تاريخ الإصدار",
|
||||
"backdate": "تأريخ رجعي",
|
||||
"due": "تاريخ الاستحقاق",
|
||||
"status": "الحالة",
|
||||
"notes": "ملاحظات",
|
||||
@@ -1846,8 +1847,6 @@
|
||||
"patientNotificationsDescription": "رسائل تُرسل إلى المرضى بشأن سجلاتهم ونتائجهم والموافقات المعلّقة",
|
||||
"accountNotifications": "إشعارات الحساب",
|
||||
"accountNotificationsDescription": "إشعارات تُرسل إليك بشأن مرضاك وفريق الرعاية",
|
||||
"features": "الميزات",
|
||||
"featuresDescription": "أدر ميزات alpha و beta لحسابك",
|
||||
"dangerZone": "منطقة الخطر",
|
||||
"dangerZoneDescription": "إجراءات لا رجعة فيها لحسابك",
|
||||
"deleteAccount": "حذف الحساب",
|
||||
@@ -1886,11 +1885,7 @@
|
||||
"pendingApprovals": "الموافقات المعلّقة",
|
||||
"pendingApprovalsDesc": "أبلغني عند موافقة مريض على تغيير معلّق أو رفضه",
|
||||
"recordsShared": "سجلات شُوركت معي",
|
||||
"recordsSharedDesc": "أبلغني عند مشاركة مريض سجلًا معي",
|
||||
"patientStorage": "تخزين مملوك للمريض (beta)",
|
||||
"patientStorageDesc": "اكتب السجلات على جهاز المريض الخاص بدلًا من قاعدة بياناتك",
|
||||
"requireSigned": "طلب سجلات موقّعة",
|
||||
"requireSignedDesc": "طلب توقيع على كل تغيير تجريه على سجل مريض"
|
||||
"recordsSharedDesc": "أبلغني عند مشاركة مريض سجلًا معي"
|
||||
}
|
||||
},
|
||||
"careTeam": {
|
||||
|
||||
@@ -466,6 +466,7 @@
|
||||
"fileNumber": "Aktennr. {{number}}",
|
||||
"change": "Ändern",
|
||||
"issued": "Ausgestellt",
|
||||
"backdate": "Rückdatieren",
|
||||
"due": "Fälligkeitsdatum",
|
||||
"status": "Status",
|
||||
"notes": "Notizen",
|
||||
@@ -1826,8 +1827,6 @@
|
||||
"patientNotificationsDescription": "E-Mails an Patienten über ihre Datensätze, Ergebnisse und ausstehenden Genehmigungen",
|
||||
"accountNotifications": "Kontobenachrichtigungen",
|
||||
"accountNotificationsDescription": "Benachrichtigungen an Sie über Ihre Patienten und das Behandlungsteam",
|
||||
"features": "Funktionen",
|
||||
"featuresDescription": "Verwalten Sie Alpha- und Beta-Funktionen für Ihr Konto",
|
||||
"dangerZone": "Gefahrenzone",
|
||||
"dangerZoneDescription": "Unumkehrbare Aktionen für Ihr Konto",
|
||||
"deleteAccount": "Konto löschen",
|
||||
@@ -1866,11 +1865,7 @@
|
||||
"pendingApprovals": "Ausstehende Genehmigungen",
|
||||
"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",
|
||||
"patientStorage": "Patienteneigener Speicher (Beta)",
|
||||
"patientStorageDesc": "Schreiben Sie Datensätze auf das eigene Gerät des Patienten statt in Ihre Datenbank",
|
||||
"requireSigned": "Signierte Datensätze erforderlich",
|
||||
"requireSignedDesc": "Erfordern Sie eine Signatur für jede Änderung, die Sie an einer Patientenakte vornehmen"
|
||||
"recordsSharedDesc": "Benachrichtigen Sie mich, wenn ein Patient einen Datensatz mit mir teilt"
|
||||
}
|
||||
},
|
||||
"careTeam": {
|
||||
|
||||
@@ -466,6 +466,7 @@
|
||||
"fileNumber": "File #{{number}}",
|
||||
"change": "Change",
|
||||
"issued": "Issued",
|
||||
"backdate": "Back-date",
|
||||
"due": "Due date",
|
||||
"status": "Status",
|
||||
"notes": "Notes",
|
||||
@@ -1826,8 +1827,6 @@
|
||||
"patientNotificationsDescription": "Emails sent to patients about their records, results, and pending approvals",
|
||||
"accountNotifications": "Account notifications",
|
||||
"accountNotificationsDescription": "Notifications sent to you about your patients and the care team",
|
||||
"features": "Features",
|
||||
"featuresDescription": "Manage alpha & beta features for your account",
|
||||
"dangerZone": "Danger Zone",
|
||||
"dangerZoneDescription": "Irreversible actions for your account",
|
||||
"deleteAccount": "Delete account",
|
||||
@@ -1866,11 +1865,7 @@
|
||||
"pendingApprovals": "Pending approvals",
|
||||
"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",
|
||||
"patientStorage": "Patient-owned storage (beta)",
|
||||
"patientStorageDesc": "Write records to the patient's own device instead of your database",
|
||||
"requireSigned": "Require signed records",
|
||||
"requireSignedDesc": "Require a signature on every change you make to a patient record"
|
||||
"recordsSharedDesc": "Notify me when a patient shares a record with me"
|
||||
}
|
||||
},
|
||||
"careTeam": {
|
||||
|
||||
@@ -466,6 +466,7 @@
|
||||
"fileNumber": "Dossier n° {{number}}",
|
||||
"change": "Changer",
|
||||
"issued": "Émise",
|
||||
"backdate": "Antidater",
|
||||
"due": "Date d'échéance",
|
||||
"status": "Statut",
|
||||
"notes": "Notes",
|
||||
@@ -1826,8 +1827,6 @@
|
||||
"patientNotificationsDescription": "E-mails envoyés aux patients à propos de leurs dossiers, résultats et approbations en attente",
|
||||
"accountNotifications": "Notifications du compte",
|
||||
"accountNotificationsDescription": "Notifications qui vous sont envoyées à propos de vos patients et de l'équipe soignante",
|
||||
"features": "Fonctionnalités",
|
||||
"featuresDescription": "Gérez les fonctionnalités alpha & bêta de votre compte",
|
||||
"dangerZone": "Zone de danger",
|
||||
"dangerZoneDescription": "Actions irréversibles pour votre compte",
|
||||
"deleteAccount": "Supprimer le compte",
|
||||
@@ -1866,11 +1865,7 @@
|
||||
"pendingApprovals": "Approbations en attente",
|
||||
"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",
|
||||
"patientStorage": "Stockage appartenant au patient (bêta)",
|
||||
"patientStorageDesc": "Écrivez les dossiers sur l'appareil du patient plutôt que dans votre base de données",
|
||||
"requireSigned": "Exiger des dossiers signés",
|
||||
"requireSignedDesc": "Exiger une signature sur chaque modification que vous apportez à un dossier patient"
|
||||
"recordsSharedDesc": "Me notifier lorsqu'un patient partage un dossier avec moi"
|
||||
}
|
||||
},
|
||||
"careTeam": {
|
||||
|
||||
@@ -466,6 +466,7 @@
|
||||
"fileNumber": "Faylka #{{number}}",
|
||||
"change": "Beddel",
|
||||
"issued": "La bixiyay",
|
||||
"backdate": "Taariikh hore",
|
||||
"due": "Taariikhda la sugayo",
|
||||
"status": "Xaalad",
|
||||
"notes": "Qoraallo",
|
||||
@@ -1826,8 +1827,6 @@
|
||||
"patientNotificationsDescription": "Iimaylada loo diro bukaannada oo ku saabsan diiwaannadooda, natiijooyinka, iyo ansixinta la sugayo",
|
||||
"accountNotifications": "Ogeysiisyada akoonka",
|
||||
"accountNotificationsDescription": "Ogeysiisyada laguu soo diro oo ku saabsan bukaannadaada iyo kooxda daryeelka",
|
||||
"features": "Astaamaha",
|
||||
"featuresDescription": "Maaree astaamaha alpha & beta ee akoonkaaga",
|
||||
"dangerZone": "Aagga khatarta",
|
||||
"dangerZoneDescription": "Ficillo aan dib loo celin karin oo akoonkaaga",
|
||||
"deleteAccount": "Tirtir akoonka",
|
||||
@@ -1866,11 +1865,7 @@
|
||||
"pendingApprovals": "Ansixinta la sugayo",
|
||||
"pendingApprovalsDesc": "I ogeysii marka bukaan ansixiyo ama diido isbeddel la sugayo",
|
||||
"recordsShared": "Diiwaanno la ila wadaagay",
|
||||
"recordsSharedDesc": "I ogeysii marka bukaan diiwaan ila wadaago",
|
||||
"patientStorage": "Kaydka bukaanka (beta)",
|
||||
"patientStorageDesc": "Ku qor diiwaannada qalabka gaarka ah ee bukaanka halkii xogtaada",
|
||||
"requireSigned": "U baahan diiwaanno la saxiixay",
|
||||
"requireSignedDesc": "U baahan saxiix isbeddel kasta oo aad ku sameyso diiwaanka bukaanka"
|
||||
"recordsSharedDesc": "I ogeysii marka bukaan diiwaan ila wadaago"
|
||||
}
|
||||
},
|
||||
"careTeam": {
|
||||
|
||||
Reference in New Issue
Block a user