mirror of
https://github.com/temetro/temetro.git
synced 2026-07-26 11:58:14 +00:00
2c239fbd27
- 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 <input type="date"> 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 <noreply@anthropic.com>
77 lines
2.4 KiB
TypeScript
77 lines
2.4 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
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";
|
|
|
|
// A small, dismissible notice shown when a newer temetro release exists. Updating
|
|
// is optional — dismissing remembers the version so we don't nag again until the
|
|
// next release. Errors are swallowed (offline / private deployments).
|
|
export function UpdateBanner() {
|
|
const { t } = useTranslation();
|
|
const [latest, setLatest] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
let active = true;
|
|
getVersionInfo()
|
|
.then((info) => {
|
|
if (!active || !info.updateAvailable || !info.latest) return;
|
|
if (localStorage.getItem(DISMISS_KEY) === info.latest) return;
|
|
setLatest(info.latest);
|
|
})
|
|
.catch(() => {
|
|
/* offline or no update server — stay silent */
|
|
});
|
|
return () => {
|
|
active = false;
|
|
};
|
|
}, []);
|
|
|
|
if (!latest) return null;
|
|
|
|
const dismiss = () => {
|
|
localStorage.setItem(DISMISS_KEY, latest);
|
|
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 (
|
|
<div className="fixed right-4 bottom-4 z-50 w-full max-w-sm" dir="auto">
|
|
<Alert className="bg-card shadow-lg" variant="warning">
|
|
<ArrowUpCircle />
|
|
<AlertTitle className="text-foreground">
|
|
{t("settings.version.banner", { version: latest })}
|
|
</AlertTitle>
|
|
<AlertAction>
|
|
<Button
|
|
render={
|
|
<Link href="/settings?tab=version" onClick={dismiss} />
|
|
}
|
|
size="sm"
|
|
variant="outline"
|
|
>
|
|
{t("settings.version.bannerUpdate")}
|
|
</Button>
|
|
<Button
|
|
aria-label={t("settings.version.bannerDismiss")}
|
|
onClick={dismiss}
|
|
size="icon-sm"
|
|
variant="ghost"
|
|
>
|
|
<X className="size-4" />
|
|
</Button>
|
|
</AlertAction>
|
|
</Alert>
|
|
</div>
|
|
);
|
|
}
|