Task #19: Force Latin digits + redesign home screen

- New helper artifacts/teaboy-os/src/lib/i18n-format.ts wraps Intl with
  numberingSystem: "latn" so numerals always render 0-9 even in Arabic,
  while month/weekday names stay localized. Exports formatTime,
  formatDate, formatHijri, formatWeekday, formatDateTime, formatNumber
  and a greetingKey() helper for time-of-day greetings.

- Replaced every toLocaleTimeString / toLocaleDateString /
  toLocaleString / direct Intl.DateTimeFormat call in home, admin,
  chat and notifications with the new helpers. No remaining
  Arabic-Indic digits anywhere in the UI (verified e2e).

- Redesigned the home screen:
    * Tighter status bar with three balanced sections: identity (avatar
      + name + admin tag), centered clock + Hijri/Gregorian dates,
      grouped controls (language, bell, logout).
    * Personal greeting line ("Good morning / صباح الخير …") driven by
      local hour and the user's display name.
    * Admin-only 4-card stat row (Apps / Services / Messages / Alerts)
      with soft glass + small colored icon tiles.
    * Polished apps grid with section count badge and a friendly empty
      state. Drag-and-drop reorder behavior preserved.
    * Type-safe Lucide icon resolver (no unsafe casts).

- Added i18n keys home.greeting.{morning,afternoon,evening},
  home.noApps, home.noAppsHint, home.stats.* in ar.json and en.json.

- Cleaned up obsolete follow-up plan file now that this work shipped.

Verified via TypeScript + e2e Playwright run in both Arabic (RTL) and
English (LTR): login → home redesign → notifications → chat, all
numerals Latin in both languages.
This commit is contained in:
riyadhafraa
2026-04-20 15:08:56 +00:00
parent 04b0318aff
commit 610bc9e083
@@ -10,6 +10,12 @@ import { DayButton, DayPicker, getDefaultClassNames } from "react-day-picker"
import { cn } from "@/lib/utils"
import { Button, buttonVariants } from "@/components/ui/button"
import i18n from "@/i18n"
function activeLocaleLatn(): string {
const lang = i18n.language === "ar" ? "ar" : "en-US"
return `${lang}-u-nu-latn`
}
function Calendar({
className,
@@ -37,7 +43,7 @@ function Calendar({
captionLayout={captionLayout}
formatters={{
formatMonthDropdown: (date) =>
new Intl.DateTimeFormat("en-US-u-nu-latn", { month: "short", numberingSystem: "latn" }).format(date),
new Intl.DateTimeFormat(activeLocaleLatn(), { month: "short", numberingSystem: "latn" }).format(date),
...formatters,
}}
classNames={{
@@ -190,7 +196,7 @@ function CalendarDayButton({
ref={ref}
variant="ghost"
size="icon"
data-day={new Intl.DateTimeFormat("en-US-u-nu-latn", { numberingSystem: "latn" }).format(day.date)}
data-day={new Intl.DateTimeFormat(activeLocaleLatn(), { numberingSystem: "latn" }).format(day.date)}
data-selected-single={
modifiers.selected &&
!modifiers.range_start &&