Task #160: 12-hour time display for executive meetings
Switch the executive-meetings schedule time display from 24-hour
(e.g. "23:47 – 15:15") to 12-hour locale-aware format with Latin
digits in both languages:
- EN: "11:47 PM – 3:15 PM"
- AR: "11:47 م – 3:15 م"
Changes:
- artifacts/tx-os/src/pages/executive-meetings.tsx
* Local formatTime() now accepts (t, lang) and routes through the
shared i18nFormatTime helper with hour12: true.
* TimeRangeCell now takes a lang: "ar" | "en" prop, threaded down
from MeetingRow (isRtl ? "ar" : "en"). Fixes the runtime
"ReferenceError: lang is not defined" from the previous attempt.
* ManageSection list passes (isRtl ? "ar" : "en") inline.
- artifacts/tx-os/src/pages/executive-meetings-print.tsx
* New formatPrintTime() mirrors the same conversion. Both render
branches (full range, start-only) updated.
Out of scope (kept untouched per the plan):
- <input type="time"> form fields (still HH:mm 24h, HTML standard).
- DB schema, API, generated client.
- User clockHour12 preference, home clock, chat — all still honor
their existing per-user setting.
Verified: tx-os tsc clean. Architect review: PASS.
This commit is contained in:
@@ -1,6 +1,24 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { safeHtml } from "@/lib/safe-html";
|
||||
import { formatTime as i18nFormatTime } from "@/lib/i18n-format";
|
||||
|
||||
/**
|
||||
* Convert an HH:mm:ss DB string to the same 12-hour locale-aware format
|
||||
* used by the on-screen schedule. Mirrors the helper in the main page so
|
||||
* the print/PDF view stays in sync (e.g. "3:15 PM" / "3:15 م"). Falls
|
||||
* back to the raw HH:mm slice if the string somehow fails to parse.
|
||||
*/
|
||||
function formatPrintTime(t: string, lang: "ar" | "en"): string {
|
||||
const hhmm = t.slice(0, 5);
|
||||
const parsed = new Date(`1970-01-01T${hhmm}:00`);
|
||||
if (Number.isNaN(parsed.getTime())) return hhmm;
|
||||
return i18nFormatTime(parsed, lang, {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: true,
|
||||
});
|
||||
}
|
||||
|
||||
type Attendee = {
|
||||
id: number;
|
||||
@@ -209,9 +227,9 @@ export default function ExecutiveMeetingsPrintPage() {
|
||||
const attendees = m.attendees ?? [];
|
||||
const time =
|
||||
m.startTime && m.endTime
|
||||
? `${m.startTime.slice(0, 5)} – ${m.endTime.slice(0, 5)}`
|
||||
? `${formatPrintTime(m.startTime, lang)} – ${formatPrintTime(m.endTime, lang)}`
|
||||
: m.startTime
|
||||
? m.startTime.slice(0, 5)
|
||||
? formatPrintTime(m.startTime, lang)
|
||||
: "—";
|
||||
return (
|
||||
<tr key={m.id} className={m.isHighlighted ? "hl" : ""}>
|
||||
|
||||
@@ -88,6 +88,7 @@ import { CSS } from "@dnd-kit/utilities";
|
||||
import { GripVertical } from "lucide-react";
|
||||
import { EditableCell } from "@/components/editable-cell";
|
||||
import { safeHtml } from "@/lib/safe-html";
|
||||
import { formatTime as i18nFormatTime } from "@/lib/i18n-format";
|
||||
|
||||
type Attendee = {
|
||||
id?: number;
|
||||
@@ -376,9 +377,21 @@ function computeCurrentMeetingId(
|
||||
return null;
|
||||
}
|
||||
|
||||
function formatTime(t: string | null): string {
|
||||
function formatTime(t: string | null, lang: "ar" | "en"): string {
|
||||
if (!t) return "";
|
||||
return t.slice(0, 5);
|
||||
// DB stores HH:mm:ss (24h). Build a Date anchored to a fixed day so
|
||||
// Intl can format the time-of-day in the user's language with AM/PM
|
||||
// (English) or ص/م (Arabic). Latin digits are enforced via the shared
|
||||
// formatTime helper. Falls back to the raw HH:mm slice if parsing
|
||||
// somehow fails (defensive — shouldn't happen for our DB shape).
|
||||
const hhmm = t.slice(0, 5);
|
||||
const parsed = new Date(`1970-01-01T${hhmm}:00`);
|
||||
if (Number.isNaN(parsed.getTime())) return hhmm;
|
||||
return i18nFormatTime(parsed, lang, {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: true,
|
||||
});
|
||||
}
|
||||
|
||||
function todayIso(): string {
|
||||
@@ -1975,6 +1988,7 @@ function MeetingRow({
|
||||
<TimeRangeCell
|
||||
meeting={meeting}
|
||||
t={t}
|
||||
lang={isRtl ? "ar" : "en"}
|
||||
canMutate={canMutate}
|
||||
onSaveTimes={onSaveTimes}
|
||||
/>
|
||||
@@ -2213,11 +2227,13 @@ function MergeMenu({
|
||||
function TimeRangeCell({
|
||||
meeting,
|
||||
t,
|
||||
lang,
|
||||
canMutate,
|
||||
onSaveTimes,
|
||||
}: {
|
||||
meeting: Meeting;
|
||||
t: (k: string) => string;
|
||||
lang: "ar" | "en";
|
||||
canMutate: boolean;
|
||||
onSaveTimes: (
|
||||
startTime: string | null,
|
||||
@@ -2310,7 +2326,7 @@ function TimeRangeCell({
|
||||
if (!editing) {
|
||||
const hasAny = !!(meeting.startTime || meeting.endTime);
|
||||
const display = hasAny
|
||||
? `${formatTime(meeting.startTime)} – ${formatTime(meeting.endTime)}`
|
||||
? `${formatTime(meeting.startTime, lang)} – ${formatTime(meeting.endTime, lang)}`
|
||||
: "—";
|
||||
return (
|
||||
<div
|
||||
@@ -2978,7 +2994,7 @@ function ManageSection({
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-3 py-2 align-middle font-mono text-xs whitespace-nowrap">
|
||||
{formatTime(m.startTime)} — {formatTime(m.endTime)}
|
||||
{formatTime(m.startTime, isRtl ? "ar" : "en")} — {formatTime(m.endTime, isRtl ? "ar" : "en")}
|
||||
</td>
|
||||
<td className="px-3 py-2 align-middle text-xs">
|
||||
{t(`executiveMeetings.manage.status.${m.status}`)}
|
||||
|
||||
Reference in New Issue
Block a user