Storage stays UTC (DB TIMESTAMPTZ, API ISO-UTC) — only the display layer is localized, and now deterministically: every timestamp renders pinned to Asia/Jerusalem via a single Intl-based formatter, so SSR (UTC container) and the browser agree on any runtime. No layout/visible-format change — only the tz. - New single date formatter web-ui/src/lib/format-date.ts (G2): formatDate / formatDateShort / formatDateLong / formatDateTime / formatDateTimeFull / formatTime / formatIsoDate + Israel helpers getIsraelYear / israelDayKey / israelMidnightMs / israelParts + formatRelative (long/short/tight wording). - Routed all ad-hoc toLocaleDateString/toLocaleTimeString/toLocaleString + hand-rolled new Date(...).get*() / toISOString().slice(0,10) timestamp call sites (30 files) through it. Number toLocaleString left untouched. - Spec: added INV-UI9 to docs/spec/X6 (UTC storage, Asia/Jerusalem display). Display-only; no layout/design/IA change → Claude Design gate N/A. Invariants: G2 (single date formatter, no parallel ad-hoc formatting), INV-UI9. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
53 lines
1.5 KiB
TypeScript
53 lines
1.5 KiB
TypeScript
"use client";
|
|
|
|
import { useGitStatus } from "@/lib/api/cases";
|
|
import { CheckCircle2, AlertCircle, Clock, CloudOff } from "lucide-react";
|
|
import { formatRelative } from "@/lib/format-date";
|
|
|
|
export function SyncIndicator({ caseNumber }: { caseNumber?: string }) {
|
|
const { data, isLoading } = useGitStatus(caseNumber);
|
|
|
|
if (isLoading || !data) return null;
|
|
|
|
if (data.error === "no_repo") {
|
|
return (
|
|
<span className="inline-flex items-center gap-1 text-[0.7rem] text-ink-muted/60" title="אין ריפו מקומי">
|
|
<CloudOff className="w-3 h-3" />
|
|
</span>
|
|
);
|
|
}
|
|
|
|
const synced = data.synced;
|
|
const pending = data.dirty_files + data.commits_ahead;
|
|
|
|
let Icon = synced ? CheckCircle2 : pending > 0 ? Clock : AlertCircle;
|
|
let color = synced
|
|
? "text-success"
|
|
: pending > 0
|
|
? "text-warn"
|
|
: "text-ink-muted";
|
|
let label = synced
|
|
? "מסונכרן"
|
|
: pending > 0
|
|
? `${pending} שינויים ממתינים`
|
|
: "לא מחובר";
|
|
|
|
if (!data.has_remote) {
|
|
Icon = CloudOff;
|
|
color = "text-ink-muted/60";
|
|
label = "אין remote";
|
|
}
|
|
|
|
const time = data.last_commit_time ? formatRelative(data.last_commit_time) : null;
|
|
const tooltip = [label, time ? `commit אחרון: ${time}` : null, data.last_commit_msg]
|
|
.filter(Boolean)
|
|
.join("\n");
|
|
|
|
return (
|
|
<span className={`inline-flex items-center gap-1 text-[0.7rem] ${color}`} title={tooltip}>
|
|
<Icon className="w-3 h-3" />
|
|
<span className="hidden sm:inline">{time ?? label}</span>
|
|
</span>
|
|
);
|
|
}
|