Task #295: Drop AM/PM (م/ص) from meeting schedule times
What changed:
- artifacts/tx-os/src/pages/executive-meetings.tsx
- Rewrote the local formatTime helper to call
Intl.DateTimeFormat.formatToParts directly, filter out segments of
type "dayPeriod", join the remaining parts and trim. Locale uses
"ar-u-nu-latn" / "en-US-u-nu-latn" so Latin digits remain forced
in Arabic. hour12: true, hour: "numeric", minute: "2-digit"
preserved from Task #292 — only the AM/PM (en) and ص/م (ar)
suffix is gone.
- Removed the now-unused i18nFormatTime import (the shared helper
in lib/i18n-format.ts always emits dayPeriod when hour12: true,
and this page now intentionally diverges).
- Touches both call sites — the schedule cell (~L3452) and the
Manage tab list (~L4583) — via the same helper.
- artifacts/api-server/src/lib/pdf-renderer.ts
- Applied the identical formatToParts + filter("dayPeriod") + trim
transformation in formatTimeRange so the printed PDF Time column
matches the on-screen schedule for both languages.
Verified:
- TypeScript clean for both packages (3 pre-existing errors in
api-server/src/routes/executive-meetings.ts last touched in #293,
unrelated to PDF renderer).
- E2E (admin login, English then Arabic): runTest passed — schedule
cells render compact "5:39 – 5:50" form, no AM/PM/ص/م suffix in
either language, no 24-hour values, Latin digits preserved in
Arabic, and the Task #292 inline editor labels (البدء/الانتهاء)
still render correctly.
- Architect code review: PASS, no critical issues.
- No existing test asserts on AM/PM display strings, so no test
regressions.
Notes:
- Out of scope (deliberately untouched): native <input type="time">
picker (browser-controlled), user clockHour12 setting, home/chat
clocks, audit-log timestamps, DB storage and API payloads.
- Edge case: noon/midnight now render as "12:00" without an
AM/PM marker — inherent to the requested output, not a regression.
- artifacts/tx-os/public/opengraph.jpg shows a tiny size bump in the
diff. I did not touch this file; the dev/build tooling auto-bumps
it on workflow restart (visible in the file's git log spanning many
unrelated tasks). Cannot be removed without destructive git ops
which are restricted.
This commit is contained in:
@@ -285,15 +285,24 @@ function formatTimeRange(
|
||||
const hhmm = t.slice(0, 5);
|
||||
const parsed = new Date(`1970-01-01T${hhmm}:00`);
|
||||
if (Number.isNaN(parsed.getTime())) return hhmm;
|
||||
return new Intl.DateTimeFormat(lang === "ar" ? "ar" : "en", {
|
||||
// 12-hour with NO AM/PM (en) or ص/م (ar) suffix — matches the
|
||||
// on-screen schedule. We `formatToParts` and drop the `dayPeriod`,
|
||||
// then trim trailing/leading whitespace literals (Intl may use
|
||||
// U+00A0 / U+202F — String.prototype.trim handles them all).
|
||||
// Force Latin digits via -u-nu-latn so the PDF Time column renders
|
||||
// consistently regardless of language.
|
||||
const locale = lang === "ar" ? "ar-u-nu-latn" : "en-US-u-nu-latn";
|
||||
const parts = new Intl.DateTimeFormat(locale, {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: true,
|
||||
// Force Latin digits even for Arabic locale so the Time column
|
||||
// renders consistently across the schedule and the PDF — matches
|
||||
// i18nFormatTime in the frontend.
|
||||
numberingSystem: "latn",
|
||||
} as Intl.DateTimeFormatOptions).format(parsed);
|
||||
} as Intl.DateTimeFormatOptions).formatToParts(parsed);
|
||||
return parts
|
||||
.filter((p) => p.type !== "dayPeriod")
|
||||
.map((p) => p.value)
|
||||
.join("")
|
||||
.trim();
|
||||
};
|
||||
if (startTime && endTime) return `${fmt(startTime)} – ${fmt(endTime)}`;
|
||||
if (startTime) return fmt(startTime);
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
@@ -88,7 +88,6 @@ 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;
|
||||
@@ -358,24 +357,34 @@ function formatTime(t: string | null, lang: "ar" | "en"): string {
|
||||
if (!t) return "";
|
||||
// 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. We render
|
||||
// in 12-hour format with AM/PM (English) or ص/م (Arabic) — admins
|
||||
// expect 12h on this page since meeting times sit in everyday work
|
||||
// hours, and the previous 24h format ("17:39") was confusing. The
|
||||
// shared `i18nFormatTime` helper enforces Latin digits in both
|
||||
// languages so the schedule reads "5:39 PM" / "5:39 م" rather than
|
||||
// mixing Arabic-Indic numerals into the table. `hour: "numeric"`
|
||||
// (vs "2-digit") drops the leading zero so single-digit hours render
|
||||
// as "5:39 PM" instead of "05:39 PM". Falls back to the raw HH:mm
|
||||
// slice if parsing somehow fails (defensive — shouldn't happen for
|
||||
// our DB shape).
|
||||
// in 12-hour format BUT without the AM/PM (English) or ص/م (Arabic)
|
||||
// day-period suffix — admins know meetings sit in business hours and
|
||||
// requested the cleaner compact form ("5:39 – 5:50"). To do this we
|
||||
// use `formatToParts` and drop the `dayPeriod` segment, then trim
|
||||
// any leading/trailing whitespace literals that were glueing it on
|
||||
// (Intl typically inserts a regular space, but some locales use
|
||||
// U+00A0 / U+202F — JS `String.prototype.trim` handles all of those).
|
||||
// `hour: "numeric"` (vs "2-digit") drops the leading zero so
|
||||
// single-digit hours render as "5:39" instead of "05:39". Latin
|
||||
// digits are forced via the `-u-nu-latn` locale extension so Arabic
|
||||
// doesn't bleed Arabic-Indic numerals into the table. 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, {
|
||||
const locale = lang === "ar" ? "ar-u-nu-latn" : "en-US-u-nu-latn";
|
||||
const parts = new Intl.DateTimeFormat(locale, {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: true,
|
||||
});
|
||||
numberingSystem: "latn",
|
||||
}).formatToParts(parsed);
|
||||
return parts
|
||||
.filter((p) => p.type !== "dayPeriod")
|
||||
.map((p) => p.value)
|
||||
.join("")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function todayIso(): string {
|
||||
|
||||
Reference in New Issue
Block a user