From c20b59e656b7ee0b5dbe0f9cc2ac94ab6103d4c7 Mon Sep 17 00:00:00 2001 From: Riyadh Date: Mon, 18 May 2026 11:04:27 +0000 Subject: [PATCH] =?UTF-8?q?Task=20#590:=20Daily=20meetings=20=E2=80=94=20c?= =?UTF-8?q?lock,=20inline=20notes,=20fullscreen=20header?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three small UX fixes on /executive-meetings (schedule tab): 1) HeaderClock: dropped the digital "HH:MM:SS م" string next to the weekday — the analog dial already conveys the live time, so it was redundant. Bumped AnalogClock size from 36 to 44 so the dial reads cleanly without the digital crutch. Weekday + date stay (calendar info, not clock info). Removed the now-unused `useState`/`useEffect` ticker inside HeaderClock — AnalogClock has its own. 2) "اكتب ملاحظة" yellow side tab no longer navigates away to /notes. Added a transient `quickNoteOpen` local state in ExecutiveMeetingsPageInner. The tab's onClick now toggles it on; a new `InlineQuickNotePanel` is rendered above with an auto-focused textarea, Cancel/Send buttons, Cmd/Ctrl+Enter to submit, Esc to close. Submit POSTs to /api/notes with the existing contract ({ content, color: "yellow" }) and closes on success. The side tab hides while the panel is open so they don't double up. State auto-resets when the user leaves the schedule tab. 3) Fullscreen exit pill overlap: the floating "الخروج من ملء الشاشة" pill (fixed top-3 end-3) was covering the schedule table's "الوقت" column header. Added `pt-12 sm:pt-14` to the fullscreen branch of
so the table starts below the pill on iPad landscape (1024-wide). Non-fullscreen layout unchanged. i18n: added executiveMeetings.quickNote.{placeholder,send} in EN+AR; existing executiveMeetings.quickNote.label reused for the panel header. All keys also have inline fallbacks so missing translations don't break. Drive-by: fixed a TypeScript narrowing error in admin.tsx left over from the prior task's review-comment cleanup (data.startedAt is already typed as string, so the typeof check produced `never` in the else branch — replaced with a direct call). Verified: pnpm --filter @workspace/tx-os exec tsc --noEmit passes clean, Vite HMR updated cleanly in dev. --- artifacts/tx-os/src/locales/ar.json | 4 +- artifacts/tx-os/src/locales/en.json | 4 +- artifacts/tx-os/src/pages/admin.tsx | 6 +- .../tx-os/src/pages/executive-meetings.tsx | 148 ++++++++++++++++-- 4 files changed, 140 insertions(+), 22 deletions(-) diff --git a/artifacts/tx-os/src/locales/ar.json b/artifacts/tx-os/src/locales/ar.json index e3f03894..c71669ae 100644 --- a/artifacts/tx-os/src/locales/ar.json +++ b/artifacts/tx-os/src/locales/ar.json @@ -1174,7 +1174,9 @@ "exit": "الخروج من ملء الشاشة" }, "quickNote": { - "label": "اكتب ملاحظة…" + "label": "اكتب ملاحظة…", + "placeholder": "اكتب ملاحظة سريعة…", + "send": "إرسال" }, "noMeetings": "لا توجد اجتماعات في هذا اليوم", "virtual": "اتصال مرئي", diff --git a/artifacts/tx-os/src/locales/en.json b/artifacts/tx-os/src/locales/en.json index 34dbe57c..5b156861 100644 --- a/artifacts/tx-os/src/locales/en.json +++ b/artifacts/tx-os/src/locales/en.json @@ -1082,7 +1082,9 @@ "exit": "Exit fullscreen" }, "quickNote": { - "label": "Take a note…" + "label": "Take a note…", + "placeholder": "Write a quick note…", + "send": "Send" }, "noMeetings": "No meetings scheduled for this day", "virtual": "Virtual", diff --git a/artifacts/tx-os/src/pages/admin.tsx b/artifacts/tx-os/src/pages/admin.tsx index a8b03443..adc30979 100644 --- a/artifacts/tx-os/src/pages/admin.tsx +++ b/artifacts/tx-os/src/pages/admin.tsx @@ -2514,11 +2514,7 @@ function SystemUpdatesPanel() { data-testid="system-updates-started-at" > {t("admin.systemUpdates.serverStartedAt")}:{" "} - {formatChecked( - typeof data.startedAt === "string" - ? data.startedAt - : data.startedAt.toISOString(), - )} + {formatChecked(data.startedAt)} )} diff --git a/artifacts/tx-os/src/pages/executive-meetings.tsx b/artifacts/tx-os/src/pages/executive-meetings.tsx index d4131787..32888dc4 100644 --- a/artifacts/tx-os/src/pages/executive-meetings.tsx +++ b/artifacts/tx-os/src/pages/executive-meetings.tsx @@ -784,25 +784,19 @@ export default function ExecutiveMeetingsPage() { } function HeaderClock({ lang, date }: { lang: "ar" | "en"; date: string }) { - const [now, setNow] = useState(() => new Date()); - useEffect(() => { - const id = window.setInterval(() => setNow(new Date()), 1000); - return () => window.clearInterval(id); - }, []); + // The analog dial already conveys the live time — the previous digital + // "HH:MM:SS" string next to it was redundant, so it was removed. We + // keep weekday + date because those are calendar info, not clock info. return (
- +
-
- {formatWeekday(date, lang, "long")} - · - - {formatTimeI18n(now, lang, { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: true })} - +
+ {formatWeekday(date, lang, "long")}
{formatDate(date, lang)} @@ -840,6 +834,114 @@ function MeetingsQuickNoteTab({ ); } +function InlineQuickNotePanel({ + lang, + onClose, +}: { + lang: "ar" | "en"; + onClose: () => void; +}) { + const { t } = useTranslation(); + const [content, setContent] = useState(""); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(null); + const textareaRef = useRef(null); + useEffect(() => { + textareaRef.current?.focus(); + }, []); + const submit = async () => { + const trimmed = content.trim(); + if (!trimmed || busy) return; + setBusy(true); + setError(null); + try { + const res = await fetch("/api/notes", { + method: "POST", + credentials: "include", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ content: trimmed, color: "yellow" }), + }); + if (!res.ok) { + const data = (await res.json().catch(() => ({}))) as { error?: string }; + throw new Error(data.error || `HTTP ${res.status}`); + } + setContent(""); + onClose(); + } catch (e) { + setError(e instanceof Error ? e.message : String(e)); + } finally { + setBusy(false); + } + }; + const placeholder = t( + "executiveMeetings.quickNote.placeholder", + lang === "ar" ? "اكتب ملاحظة سريعة…" : "Write a quick note…", + ); + const sendLabel = t("executiveMeetings.quickNote.send", lang === "ar" ? "إرسال" : "Send"); + const cancelLabel = t("common.cancel", lang === "ar" ? "إلغاء" : "Cancel"); + return ( +
+
+
+ {t("executiveMeetings.quickNote.label", lang === "ar" ? "اكتب ملاحظة…" : "Take a note…")} +
+ +
+
+