@open-mercato/core 0.6.8-develop.6962.1.bd029a2bb2 → 0.6.8-develop.6963.1.04a6d320e9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -35,11 +35,11 @@ function formatUrlHost(location) {
35
35
  return location;
36
36
  }
37
37
  }
38
- function formatTime(date) {
39
- return date.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
38
+ function formatTime(locale, date) {
39
+ return date.toLocaleTimeString(locale, { hour: "numeric", minute: "2-digit" });
40
40
  }
41
- function groupLabelOf(date) {
42
- return date.toLocaleDateString(void 0, { weekday: "long", month: "short", day: "numeric" });
41
+ function groupLabelOf(locale, date) {
42
+ return date.toLocaleDateString(locale, { weekday: "long", month: "short", day: "numeric" });
43
43
  }
44
44
  function deriveTypeLabel(interactionType) {
45
45
  const normalized = interactionType.replace(/[-_]+/g, " ").trim();
@@ -76,7 +76,7 @@ function AgendaDayHeader({ date, count }) {
76
76
  const resolvedCount = t(countKey, { count });
77
77
  const countLabel = resolvedCount === countKey ? t("customers.calendar.agenda.eventsCount.other", "{count} events", { count }) : resolvedCount;
78
78
  return /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-2 bg-muted/50 px-3 py-2.5 sm:px-5", children: [
79
- /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-foreground", children: groupLabelOf(date) }),
79
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-foreground", children: groupLabelOf(locale, date) }),
80
80
  todayMarker || tomorrowMarker ? /* @__PURE__ */ jsx("span", { className: cn("text-xs font-medium", todayMarker ? "text-foreground" : "text-muted-foreground"), children: `\xB7 ${todayMarker ? t("customers.calendar.agenda.today", "Today") : t("customers.calendar.agenda.tomorrow", "Tomorrow")}` }) : null,
81
81
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "w-2.5 shrink-0" }),
82
82
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: countLabel })
@@ -88,6 +88,7 @@ function AgendaRow({
88
88
  onItemClick
89
89
  }) {
90
90
  const t = useT();
91
+ const locale = useLocale();
91
92
  const canceled = item.status === "canceled";
92
93
  const done = item.status === "done";
93
94
  const title = eventDisplayTitle(item.title, t("customers.calendar.grid.untitled", "Untitled"));
@@ -99,8 +100,8 @@ function AgendaRow({
99
100
  teams: t("customers.calendar.platformFull.teams", "Microsoft Teams")
100
101
  };
101
102
  const locationLabel = item.platform ? platformLabels[item.platform] : item.locationKind === "url" && item.location ? formatUrlHost(item.location) : item.location;
102
- const startLabel = item.allDay ? t("customers.calendar.grid.allDay", "All day") : formatTime(item.start);
103
- const endLabel = item.allDay ? null : formatTime(item.end);
103
+ const startLabel = item.allDay ? t("customers.calendar.grid.allDay", "All day") : formatTime(locale, item.start);
104
+ const endLabel = item.allDay ? null : formatTime(locale, item.end);
104
105
  const ariaTime = item.allDay ? startLabel : `${startLabel} \u2013 ${endLabel}`;
105
106
  return /* @__PURE__ */ jsxs(
106
107
  Button,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/calendar/AgendaList.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { addDays } from 'date-fns/addDays'\nimport { format } from 'date-fns/format'\nimport { isToday } from 'date-fns/isToday'\nimport { isTomorrow } from 'date-fns/isTomorrow'\nimport { startOfDay } from 'date-fns/startOfDay'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { eventDisplayTitle, pluralCategory } from '../../lib/calendar/labels'\nimport type { AgendaListProps, CalendarCategory, CalendarItem } from './types'\n\nconst MAX_AVATARS_PER_ROW = 2\n\nconst CATEGORY_BADGE_CLASS: Record<CalendarCategory, string> = {\n meeting: 'bg-status-info-bg text-status-info-text',\n event: 'bg-status-warning-bg text-status-warning-text',\n task: 'bg-primary text-primary-foreground',\n other: 'bg-status-neutral-bg text-status-neutral-text',\n}\n\nconst CATEGORY_LABEL: Record<CalendarCategory, { key: string; fallback: string }> = {\n meeting: { key: 'customers.calendar.category.meeting', fallback: 'Meeting' },\n event: { key: 'customers.calendar.category.event', fallback: 'Event' },\n task: { key: 'customers.calendar.category.task', fallback: 'Task' },\n other: { key: 'customers.calendar.category.other', fallback: 'Other' },\n}\n\ntype AgendaDayGroup = { date: Date; items: CalendarItem[] }\n\nfunction dayKeyOf(date: Date): string {\n return format(date, 'yyyy-MM-dd')\n}\n\nfunction formatUrlHost(location: string): string {\n try {\n const url = new URL(location.startsWith('http') ? location : `https://${location}`)\n return url.hostname\n } catch {\n return location\n }\n}\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' })\n}\n\nfunction groupLabelOf(date: Date): string {\n return date.toLocaleDateString(undefined, { weekday: 'long', month: 'short', day: 'numeric' })\n}\n\nfunction deriveTypeLabel(interactionType: string): string {\n const normalized = interactionType.replace(/[-_]+/g, ' ').trim()\n if (!normalized) return interactionType\n return normalized.charAt(0).toUpperCase() + normalized.slice(1)\n}\n\nfunction participantLabel(participant: CalendarItem['participants'][number]): string {\n return participant.name ?? participant.email ?? participant.userId\n}\n\nfunction buildDayGroups(anchor: Date, horizonDays: number, items: CalendarItem[]): AgendaDayGroup[] {\n const sorted = [...items].sort((first, second) => first.start.getTime() - second.start.getTime())\n const byDay = new Map<string, CalendarItem[]>()\n for (const item of sorted) {\n const key = dayKeyOf(item.start)\n const bucket = byDay.get(key)\n if (bucket) bucket.push(item)\n else byDay.set(key, [item])\n }\n const firstDay = startOfDay(anchor)\n const groups: AgendaDayGroup[] = []\n for (let offset = 0; offset <= horizonDays; offset += 1) {\n const day = addDays(firstDay, offset)\n const dayItems = byDay.get(dayKeyOf(day))\n if (dayItems && dayItems.length > 0) groups.push({ date: day, items: dayItems })\n }\n return groups\n}\n\nfunction AgendaDayHeader({ date, count }: { date: Date; count: number }) {\n const t = useT()\n const locale = useLocale()\n const todayMarker = isToday(date)\n const tomorrowMarker = !todayMarker && isTomorrow(date)\n const countKey = `customers.calendar.agenda.eventsCount.${pluralCategory(locale, count)}`\n const resolvedCount = t(countKey, { count })\n const countLabel =\n resolvedCount === countKey\n ? t('customers.calendar.agenda.eventsCount.other', '{count} events', { count })\n : resolvedCount\n return (\n <div className=\"flex w-full items-center gap-2 bg-muted/50 px-3 py-2.5 sm:px-5\">\n <span className=\"text-sm font-semibold text-foreground\">{groupLabelOf(date)}</span>\n {todayMarker || tomorrowMarker ? (\n <span className={cn('text-xs font-medium', todayMarker ? 'text-foreground' : 'text-muted-foreground')}>\n {`\u00B7 ${todayMarker ? t('customers.calendar.agenda.today', 'Today') : t('customers.calendar.agenda.tomorrow', 'Tomorrow')}`}\n </span>\n ) : null}\n <span aria-hidden=\"true\" className=\"w-2.5 shrink-0\" />\n <span className=\"text-xs font-medium text-muted-foreground\">{countLabel}</span>\n </div>\n )\n}\n\nfunction AgendaRow({\n item,\n typeLabels,\n onItemClick,\n}: {\n item: CalendarItem\n typeLabels?: Record<string, string>\n onItemClick: (item: CalendarItem) => void\n}) {\n const t = useT()\n const canceled = item.status === 'canceled'\n const done = item.status === 'done'\n const title = eventDisplayTitle(item.title, t('customers.calendar.grid.untitled', 'Untitled'))\n const typeLabel = typeLabels?.[item.interactionType] ?? deriveTypeLabel(item.interactionType)\n const platformLabels: Record<string, string> = {\n zoom: t('customers.calendar.platformFull.zoom', 'Zoom'),\n meet: t('customers.calendar.platformFull.meet', 'Google Meet'),\n slack: t('customers.calendar.platformFull.slack', 'Slack'),\n teams: t('customers.calendar.platformFull.teams', 'Microsoft Teams'),\n }\n const locationLabel = item.platform\n ? platformLabels[item.platform]\n : item.locationKind === 'url' && item.location\n ? formatUrlHost(item.location)\n : item.location\n const startLabel = item.allDay ? t('customers.calendar.grid.allDay', 'All day') : formatTime(item.start)\n const endLabel = item.allDay ? null : formatTime(item.end)\n const ariaTime = item.allDay ? startLabel : `${startLabel} \u2013 ${endLabel}`\n return (\n <Button\n type=\"button\"\n variant=\"ghost\"\n aria-label={`${title} \u00B7 ${ariaTime}`}\n onClick={() => onItemClick(item)}\n className={cn(\n 'h-auto w-full justify-start whitespace-normal rounded-none bg-background px-3 py-3 text-left transition-colors hover:bg-muted/30 sm:gap-3.5 sm:px-5',\n canceled && 'opacity-60',\n )}\n >\n <div className=\"flex w-16 shrink-0 flex-col gap-0.5 sm:w-[86px]\">\n <span className={cn('truncate text-sm font-semibold leading-4', done || canceled ? 'text-muted-foreground' : 'text-foreground')}>\n {startLabel}\n </span>\n {endLabel ? <span className=\"truncate text-xs text-muted-foreground\">{endLabel}</span> : null}\n </div>\n <span\n aria-hidden=\"true\"\n className={cn('h-9 w-1 shrink-0 rounded-full', !item.color && 'bg-muted-foreground/40')}\n style={item.color ? { backgroundColor: item.color } : undefined}\n />\n <div className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span\n className={cn(\n 'truncate text-sm font-semibold',\n done || canceled ? 'text-muted-foreground' : 'text-foreground',\n canceled && 'line-through',\n )}\n >\n {title}\n </span>\n <span className=\"truncate text-xs text-muted-foreground\">\n {locationLabel ? `${typeLabel} \u00B7 ${locationLabel}` : typeLabel}\n </span>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n {item.participants.length > 0 ? (\n <span className=\"hidden items-center gap-0.5 sm:flex\">\n {item.participants.slice(0, MAX_AVATARS_PER_ROW).map((participant) => (\n <Avatar key={participant.userId} size=\"xs\" label={participantLabel(participant)} />\n ))}\n {item.participants.length > MAX_AVATARS_PER_ROW ? (\n <span className=\"ps-0.5 text-xs font-medium text-muted-foreground\">\n +{item.participants.length - MAX_AVATARS_PER_ROW}\n </span>\n ) : null}\n </span>\n ) : null}\n <span\n className={cn(\n 'inline-flex shrink-0 items-center rounded-full px-2 py-0.5 text-overline font-medium uppercase tracking-wide',\n CATEGORY_BADGE_CLASS[item.category],\n )}\n >\n {t(CATEGORY_LABEL[item.category].key, CATEGORY_LABEL[item.category].fallback)}\n </span>\n </div>\n </Button>\n )\n}\n\nexport function AgendaList({ anchor, horizonDays, items, typeLabels, onItemClick }: AgendaListProps) {\n const t = useT()\n const groups = React.useMemo(() => buildDayGroups(anchor, horizonDays, items), [anchor, horizonDays, items])\n return (\n <div className=\"flex w-full flex-col divide-y divide-border overflow-hidden border border-border bg-background\">\n {groups.map((group) => (\n <React.Fragment key={dayKeyOf(group.date)}>\n <AgendaDayHeader date={group.date} count={group.items.length} />\n {group.items.map((item) => (\n <AgendaRow key={item.id} item={item} typeLabels={typeLabels} onItemClick={onItemClick} />\n ))}\n </React.Fragment>\n ))}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { addDays } from 'date-fns/addDays'\nimport { format } from 'date-fns/format'\nimport { isToday } from 'date-fns/isToday'\nimport { isTomorrow } from 'date-fns/isTomorrow'\nimport { startOfDay } from 'date-fns/startOfDay'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { eventDisplayTitle, pluralCategory } from '../../lib/calendar/labels'\nimport type { AgendaListProps, CalendarCategory, CalendarItem } from './types'\n\nconst MAX_AVATARS_PER_ROW = 2\n\nconst CATEGORY_BADGE_CLASS: Record<CalendarCategory, string> = {\n meeting: 'bg-status-info-bg text-status-info-text',\n event: 'bg-status-warning-bg text-status-warning-text',\n task: 'bg-primary text-primary-foreground',\n other: 'bg-status-neutral-bg text-status-neutral-text',\n}\n\nconst CATEGORY_LABEL: Record<CalendarCategory, { key: string; fallback: string }> = {\n meeting: { key: 'customers.calendar.category.meeting', fallback: 'Meeting' },\n event: { key: 'customers.calendar.category.event', fallback: 'Event' },\n task: { key: 'customers.calendar.category.task', fallback: 'Task' },\n other: { key: 'customers.calendar.category.other', fallback: 'Other' },\n}\n\ntype AgendaDayGroup = { date: Date; items: CalendarItem[] }\n\nfunction dayKeyOf(date: Date): string {\n return format(date, 'yyyy-MM-dd')\n}\n\nfunction formatUrlHost(location: string): string {\n try {\n const url = new URL(location.startsWith('http') ? location : `https://${location}`)\n return url.hostname\n } catch {\n return location\n }\n}\n\nfunction formatTime(locale: string, date: Date): string {\n return date.toLocaleTimeString(locale, { hour: 'numeric', minute: '2-digit' })\n}\n\nfunction groupLabelOf(locale: string, date: Date): string {\n return date.toLocaleDateString(locale, { weekday: 'long', month: 'short', day: 'numeric' })\n}\n\nfunction deriveTypeLabel(interactionType: string): string {\n const normalized = interactionType.replace(/[-_]+/g, ' ').trim()\n if (!normalized) return interactionType\n return normalized.charAt(0).toUpperCase() + normalized.slice(1)\n}\n\nfunction participantLabel(participant: CalendarItem['participants'][number]): string {\n return participant.name ?? participant.email ?? participant.userId\n}\n\nfunction buildDayGroups(anchor: Date, horizonDays: number, items: CalendarItem[]): AgendaDayGroup[] {\n const sorted = [...items].sort((first, second) => first.start.getTime() - second.start.getTime())\n const byDay = new Map<string, CalendarItem[]>()\n for (const item of sorted) {\n const key = dayKeyOf(item.start)\n const bucket = byDay.get(key)\n if (bucket) bucket.push(item)\n else byDay.set(key, [item])\n }\n const firstDay = startOfDay(anchor)\n const groups: AgendaDayGroup[] = []\n for (let offset = 0; offset <= horizonDays; offset += 1) {\n const day = addDays(firstDay, offset)\n const dayItems = byDay.get(dayKeyOf(day))\n if (dayItems && dayItems.length > 0) groups.push({ date: day, items: dayItems })\n }\n return groups\n}\n\nfunction AgendaDayHeader({ date, count }: { date: Date; count: number }) {\n const t = useT()\n const locale = useLocale()\n const todayMarker = isToday(date)\n const tomorrowMarker = !todayMarker && isTomorrow(date)\n const countKey = `customers.calendar.agenda.eventsCount.${pluralCategory(locale, count)}`\n const resolvedCount = t(countKey, { count })\n const countLabel =\n resolvedCount === countKey\n ? t('customers.calendar.agenda.eventsCount.other', '{count} events', { count })\n : resolvedCount\n return (\n <div className=\"flex w-full items-center gap-2 bg-muted/50 px-3 py-2.5 sm:px-5\">\n <span className=\"text-sm font-semibold text-foreground\">{groupLabelOf(locale, date)}</span>\n {todayMarker || tomorrowMarker ? (\n <span className={cn('text-xs font-medium', todayMarker ? 'text-foreground' : 'text-muted-foreground')}>\n {`\u00B7 ${todayMarker ? t('customers.calendar.agenda.today', 'Today') : t('customers.calendar.agenda.tomorrow', 'Tomorrow')}`}\n </span>\n ) : null}\n <span aria-hidden=\"true\" className=\"w-2.5 shrink-0\" />\n <span className=\"text-xs font-medium text-muted-foreground\">{countLabel}</span>\n </div>\n )\n}\n\nfunction AgendaRow({\n item,\n typeLabels,\n onItemClick,\n}: {\n item: CalendarItem\n typeLabels?: Record<string, string>\n onItemClick: (item: CalendarItem) => void\n}) {\n const t = useT()\n const locale = useLocale()\n const canceled = item.status === 'canceled'\n const done = item.status === 'done'\n const title = eventDisplayTitle(item.title, t('customers.calendar.grid.untitled', 'Untitled'))\n const typeLabel = typeLabels?.[item.interactionType] ?? deriveTypeLabel(item.interactionType)\n const platformLabels: Record<string, string> = {\n zoom: t('customers.calendar.platformFull.zoom', 'Zoom'),\n meet: t('customers.calendar.platformFull.meet', 'Google Meet'),\n slack: t('customers.calendar.platformFull.slack', 'Slack'),\n teams: t('customers.calendar.platformFull.teams', 'Microsoft Teams'),\n }\n const locationLabel = item.platform\n ? platformLabels[item.platform]\n : item.locationKind === 'url' && item.location\n ? formatUrlHost(item.location)\n : item.location\n const startLabel = item.allDay ? t('customers.calendar.grid.allDay', 'All day') : formatTime(locale, item.start)\n const endLabel = item.allDay ? null : formatTime(locale, item.end)\n const ariaTime = item.allDay ? startLabel : `${startLabel} \u2013 ${endLabel}`\n return (\n <Button\n type=\"button\"\n variant=\"ghost\"\n aria-label={`${title} \u00B7 ${ariaTime}`}\n onClick={() => onItemClick(item)}\n className={cn(\n 'h-auto w-full justify-start whitespace-normal rounded-none bg-background px-3 py-3 text-left transition-colors hover:bg-muted/30 sm:gap-3.5 sm:px-5',\n canceled && 'opacity-60',\n )}\n >\n <div className=\"flex w-16 shrink-0 flex-col gap-0.5 sm:w-[86px]\">\n <span className={cn('truncate text-sm font-semibold leading-4', done || canceled ? 'text-muted-foreground' : 'text-foreground')}>\n {startLabel}\n </span>\n {endLabel ? <span className=\"truncate text-xs text-muted-foreground\">{endLabel}</span> : null}\n </div>\n <span\n aria-hidden=\"true\"\n className={cn('h-9 w-1 shrink-0 rounded-full', !item.color && 'bg-muted-foreground/40')}\n style={item.color ? { backgroundColor: item.color } : undefined}\n />\n <div className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span\n className={cn(\n 'truncate text-sm font-semibold',\n done || canceled ? 'text-muted-foreground' : 'text-foreground',\n canceled && 'line-through',\n )}\n >\n {title}\n </span>\n <span className=\"truncate text-xs text-muted-foreground\">\n {locationLabel ? `${typeLabel} \u00B7 ${locationLabel}` : typeLabel}\n </span>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n {item.participants.length > 0 ? (\n <span className=\"hidden items-center gap-0.5 sm:flex\">\n {item.participants.slice(0, MAX_AVATARS_PER_ROW).map((participant) => (\n <Avatar key={participant.userId} size=\"xs\" label={participantLabel(participant)} />\n ))}\n {item.participants.length > MAX_AVATARS_PER_ROW ? (\n <span className=\"ps-0.5 text-xs font-medium text-muted-foreground\">\n +{item.participants.length - MAX_AVATARS_PER_ROW}\n </span>\n ) : null}\n </span>\n ) : null}\n <span\n className={cn(\n 'inline-flex shrink-0 items-center rounded-full px-2 py-0.5 text-overline font-medium uppercase tracking-wide',\n CATEGORY_BADGE_CLASS[item.category],\n )}\n >\n {t(CATEGORY_LABEL[item.category].key, CATEGORY_LABEL[item.category].fallback)}\n </span>\n </div>\n </Button>\n )\n}\n\nexport function AgendaList({ anchor, horizonDays, items, typeLabels, onItemClick }: AgendaListProps) {\n const t = useT()\n const groups = React.useMemo(() => buildDayGroups(anchor, horizonDays, items), [anchor, horizonDays, items])\n return (\n <div className=\"flex w-full flex-col divide-y divide-border overflow-hidden border border-border bg-background\">\n {groups.map((group) => (\n <React.Fragment key={dayKeyOf(group.date)}>\n <AgendaDayHeader date={group.date} count={group.items.length} />\n {group.items.map((item) => (\n <AgendaRow key={item.id} item={item} typeLabels={typeLabels} onItemClick={onItemClick} />\n ))}\n </React.Fragment>\n ))}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -5,7 +5,7 @@ import { addDays } from "date-fns/addDays";
5
5
  import { format } from "date-fns/format";
6
6
  import { isSameMonth } from "date-fns/isSameMonth";
7
7
  import { isToday } from "date-fns/isToday";
8
- import { useT } from "@open-mercato/shared/lib/i18n/context";
8
+ import { useLocale, useT } from "@open-mercato/shared/lib/i18n/context";
9
9
  import { cn } from "@open-mercato/shared/lib/utils";
10
10
  import { Button } from "@open-mercato/ui/primitives/button";
11
11
  import { getVisibleRange } from "../../lib/calendar/range.js";
@@ -15,11 +15,11 @@ const SOFT_TINT_ALPHA = "1A";
15
15
  function dayKeyOf(date) {
16
16
  return format(date, "yyyy-MM-dd");
17
17
  }
18
- function formatTime(date) {
19
- return date.toLocaleTimeString(void 0, { hour: "numeric", minute: "2-digit" });
18
+ function formatTime(locale, date) {
19
+ return date.toLocaleTimeString(locale, { hour: "numeric", minute: "2-digit" });
20
20
  }
21
- function fullDateLabel(date) {
22
- return date.toLocaleDateString(void 0, { weekday: "long", year: "numeric", month: "long", day: "numeric" });
21
+ function fullDateLabel(locale, date) {
22
+ return date.toLocaleDateString(locale, { weekday: "long", year: "numeric", month: "long", day: "numeric" });
23
23
  }
24
24
  function isWeekend(date) {
25
25
  const weekday = date.getDay();
@@ -52,9 +52,10 @@ function groupItemsByDay(items) {
52
52
  }
53
53
  function MonthPill({ item, onItemClick }) {
54
54
  const t = useT();
55
+ const locale = useLocale();
55
56
  const canceled = item.status === "canceled";
56
57
  const title = eventDisplayTitle(item.title, t("customers.calendar.grid.untitled", "Untitled"));
57
- const timeLabel = item.allDay ? "" : ` \xB7 ${formatTime(item.start)} \u2013 ${formatTime(item.end)}`;
58
+ const timeLabel = item.allDay ? "" : ` \xB7 ${formatTime(locale, item.start)} \u2013 ${formatTime(locale, item.end)}`;
58
59
  const tintStyle = item.color ? { backgroundColor: `${item.color}${SOFT_TINT_ALPHA}`, color: item.color } : void 0;
59
60
  return /* @__PURE__ */ jsxs(
60
61
  Button,
@@ -102,11 +103,12 @@ function MonthDayCell({
102
103
  onDayOpen
103
104
  }) {
104
105
  const t = useT();
106
+ const locale = useLocale();
105
107
  const inMonth = isSameMonth(day, anchor);
106
108
  const today = isToday(day);
107
109
  const visibleItems = items.slice(0, MAX_PILLS_PER_DAY);
108
110
  const hiddenCount = items.length - visibleItems.length;
109
- const dayLabel = fullDateLabel(day);
111
+ const dayLabel = fullDateLabel(locale, day);
110
112
  return /* @__PURE__ */ jsxs(
111
113
  "div",
112
114
  {
@@ -164,11 +166,12 @@ function MonthDayCell({
164
166
  }
165
167
  function MonthGrid({ anchor, items, onItemClick, onDayOpen }) {
166
168
  const t = useT();
169
+ const locale = useLocale();
167
170
  const weeks = React.useMemo(() => buildWeeks(anchor), [anchor]);
168
171
  const itemsByDay = React.useMemo(() => groupItemsByDay(items), [items]);
169
172
  return /* @__PURE__ */ jsxs("div", { className: "relative flex h-full w-full flex-col overflow-hidden border border-border bg-background", children: [
170
173
  /* @__PURE__ */ jsx("div", { className: "flex h-9 w-full shrink-0 border-b border-border", children: weeks[0]?.map((day) => {
171
- const label = day.toLocaleDateString(void 0, { weekday: "short" }).toUpperCase();
174
+ const label = day.toLocaleDateString(locale, { weekday: "short" }).toLocaleUpperCase(locale);
172
175
  return /* @__PURE__ */ jsx(
173
176
  "div",
174
177
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/calendar/MonthGrid.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { addDays } from 'date-fns/addDays'\nimport { format } from 'date-fns/format'\nimport { isSameMonth } from 'date-fns/isSameMonth'\nimport { isToday } from 'date-fns/isToday'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { getVisibleRange } from '../../lib/calendar/range'\nimport { eventDisplayTitle } from '../../lib/calendar/labels'\nimport type { CalendarItem, MonthGridProps } from './types'\n\nconst MAX_PILLS_PER_DAY = 2\nconst SOFT_TINT_ALPHA = '1A'\n\nfunction dayKeyOf(date: Date): string {\n return format(date, 'yyyy-MM-dd')\n}\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' })\n}\n\nfunction fullDateLabel(date: Date): string {\n return date.toLocaleDateString(undefined, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })\n}\n\nfunction isWeekend(date: Date): boolean {\n const weekday = date.getDay()\n return weekday === 0 || weekday === 6\n}\n\nfunction buildWeeks(anchor: Date): Date[][] {\n const range = getVisibleRange('month', anchor, 0)\n const weeks: Date[][] = []\n let cursor = range.from\n while (cursor.getTime() <= range.to.getTime()) {\n const week: Date[] = []\n for (let dayIndex = 0; dayIndex < 7; dayIndex += 1) {\n week.push(cursor)\n cursor = addDays(cursor, 1)\n }\n weeks.push(week)\n }\n return weeks\n}\n\nfunction groupItemsByDay(items: CalendarItem[]): Map<string, CalendarItem[]> {\n const sorted = [...items].sort((first, second) => first.start.getTime() - second.start.getTime())\n const byDay = new Map<string, CalendarItem[]>()\n for (const item of sorted) {\n const key = dayKeyOf(item.start)\n const bucket = byDay.get(key)\n if (bucket) bucket.push(item)\n else byDay.set(key, [item])\n }\n return byDay\n}\n\nfunction MonthPill({ item, onItemClick }: { item: CalendarItem; onItemClick: (item: CalendarItem) => void }) {\n const t = useT()\n const canceled = item.status === 'canceled'\n const title = eventDisplayTitle(item.title, t('customers.calendar.grid.untitled', 'Untitled'))\n const timeLabel = item.allDay ? '' : ` \u00B7 ${formatTime(item.start)} \u2013 ${formatTime(item.end)}`\n const tintStyle = item.color\n ? { backgroundColor: `${item.color}${SOFT_TINT_ALPHA}`, color: item.color }\n : undefined\n return (\n <Button\n type=\"button\"\n variant=\"ghost\"\n aria-label={`${title}${timeLabel}`}\n onClick={(event) => {\n event.stopPropagation()\n onItemClick(item)\n }}\n className={cn(\n 'h-4 w-full min-w-0 justify-center gap-0 rounded-full p-0 pr-2',\n !item.color && 'bg-muted text-muted-foreground',\n canceled && 'opacity-60',\n )}\n style={tintStyle}\n >\n <span aria-hidden=\"true\" className=\"flex size-4 shrink-0 items-center justify-center\">\n <span\n className={cn('size-1.5 rounded-full', !item.color && 'bg-muted-foreground')}\n style={item.color ? { backgroundColor: item.color } : undefined}\n />\n </span>\n <span\n className={cn(\n 'min-w-0 truncate text-overline font-medium uppercase tracking-wide',\n canceled && 'line-through',\n )}\n >\n {title}\n </span>\n </Button>\n )\n}\n\nfunction MonthDayCell({\n day,\n anchor,\n items,\n onItemClick,\n onDayOpen,\n}: {\n day: Date\n anchor: Date\n items: CalendarItem[]\n onItemClick: (item: CalendarItem) => void\n onDayOpen: (date: Date) => void\n}) {\n const t = useT()\n const inMonth = isSameMonth(day, anchor)\n const today = isToday(day)\n const visibleItems = items.slice(0, MAX_PILLS_PER_DAY)\n const hiddenCount = items.length - visibleItems.length\n const dayLabel = fullDateLabel(day)\n return (\n <div\n className={cn(\n 'relative flex h-full min-w-0 flex-1 flex-col items-start gap-1 overflow-hidden border-r border-border p-2 text-left last:border-r-0',\n isWeekend(day) ? 'bg-muted/40' : 'bg-background',\n )}\n >\n <Button\n type=\"button\"\n variant=\"ghost\"\n aria-label={dayLabel}\n onClick={() => onDayOpen(day)}\n className=\"absolute inset-0 z-0 h-full w-full rounded-none p-0 hover:bg-muted/50\"\n >\n <span className=\"sr-only\">{dayLabel}</span>\n </Button>\n {today ? (\n <span className=\"pointer-events-none relative z-10 flex items-center justify-center rounded-full bg-primary px-1.5 py-0.5 text-xs font-semibold text-primary-foreground\">\n {day.getDate()}\n </span>\n ) : (\n <span className={cn('pointer-events-none relative z-10 text-xs font-medium', inMonth ? 'text-foreground' : 'text-muted-foreground')}>\n {day.getDate()}\n </span>\n )}\n <span className=\"relative z-20 hidden w-full flex-col gap-1 sm:flex\">\n {visibleItems.map((item) => (\n <MonthPill key={item.id} item={item} onItemClick={onItemClick} />\n ))}\n {hiddenCount > 0 ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto self-start p-0 text-overline font-medium text-muted-foreground hover:bg-transparent hover:underline\"\n onClick={(event) => {\n event.stopPropagation()\n onDayOpen(day)\n }}\n >\n {t('customers.calendar.grid.more', '+{count} more', { count: hiddenCount })}\n </Button>\n ) : null}\n </span>\n {items.length > 0 ? (\n <span aria-hidden=\"true\" className=\"pointer-events-none relative z-10 flex w-full flex-wrap items-center gap-0.5 sm:hidden\">\n {items.slice(0, 4).map((item) => (\n <span\n key={item.id}\n className={cn('size-1.5 rounded-full', !item.color && 'bg-muted-foreground')}\n style={item.color ? { backgroundColor: item.color } : undefined}\n />\n ))}\n {items.length > 4 ? (\n <span className=\"text-overline font-medium leading-none text-muted-foreground\">\n +{items.length - 4}\n </span>\n ) : null}\n </span>\n ) : null}\n </div>\n )\n}\n\nexport function MonthGrid({ anchor, items, onItemClick, onDayOpen }: MonthGridProps) {\n const t = useT()\n const weeks = React.useMemo(() => buildWeeks(anchor), [anchor])\n const itemsByDay = React.useMemo(() => groupItemsByDay(items), [items])\n return (\n <div className=\"relative flex h-full w-full flex-col overflow-hidden border border-border bg-background\">\n <div className=\"flex h-9 w-full shrink-0 border-b border-border\">\n {weeks[0]?.map((day) => {\n const label = day.toLocaleDateString(undefined, { weekday: 'short' }).toUpperCase()\n return (\n <div\n key={dayKeyOf(day)}\n className=\"flex min-w-0 flex-1 items-center justify-center border-r border-border last:border-r-0 sm:justify-start sm:pl-2.5\"\n >\n <span className=\"truncate text-overline font-medium uppercase tracking-widest text-muted-foreground\">\n <span className=\"sm:hidden\">{label.charAt(0)}</span>\n <span className=\"hidden sm:inline\">{label}</span>\n </span>\n </div>\n )\n })}\n </div>\n {weeks.map((week) => (\n <div\n key={dayKeyOf(week[0])}\n className=\"flex w-full flex-1 border-b border-border last:border-b-0 min-h-14 sm:min-h-[86px]\"\n >\n {week.map((day) => (\n <MonthDayCell\n key={dayKeyOf(day)}\n day={day}\n anchor={anchor}\n items={itemsByDay.get(dayKeyOf(day)) ?? []}\n onItemClick={onItemClick}\n onDayOpen={onDayOpen}\n />\n ))}\n </div>\n ))}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { addDays } from 'date-fns/addDays'\nimport { format } from 'date-fns/format'\nimport { isSameMonth } from 'date-fns/isSameMonth'\nimport { isToday } from 'date-fns/isToday'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { getVisibleRange } from '../../lib/calendar/range'\nimport { eventDisplayTitle } from '../../lib/calendar/labels'\nimport type { CalendarItem, MonthGridProps } from './types'\n\nconst MAX_PILLS_PER_DAY = 2\nconst SOFT_TINT_ALPHA = '1A'\n\nfunction dayKeyOf(date: Date): string {\n return format(date, 'yyyy-MM-dd')\n}\n\nfunction formatTime(locale: string, date: Date): string {\n return date.toLocaleTimeString(locale, { hour: 'numeric', minute: '2-digit' })\n}\n\nfunction fullDateLabel(locale: string, date: Date): string {\n return date.toLocaleDateString(locale, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })\n}\n\nfunction isWeekend(date: Date): boolean {\n const weekday = date.getDay()\n return weekday === 0 || weekday === 6\n}\n\nfunction buildWeeks(anchor: Date): Date[][] {\n const range = getVisibleRange('month', anchor, 0)\n const weeks: Date[][] = []\n let cursor = range.from\n while (cursor.getTime() <= range.to.getTime()) {\n const week: Date[] = []\n for (let dayIndex = 0; dayIndex < 7; dayIndex += 1) {\n week.push(cursor)\n cursor = addDays(cursor, 1)\n }\n weeks.push(week)\n }\n return weeks\n}\n\nfunction groupItemsByDay(items: CalendarItem[]): Map<string, CalendarItem[]> {\n const sorted = [...items].sort((first, second) => first.start.getTime() - second.start.getTime())\n const byDay = new Map<string, CalendarItem[]>()\n for (const item of sorted) {\n const key = dayKeyOf(item.start)\n const bucket = byDay.get(key)\n if (bucket) bucket.push(item)\n else byDay.set(key, [item])\n }\n return byDay\n}\n\nfunction MonthPill({ item, onItemClick }: { item: CalendarItem; onItemClick: (item: CalendarItem) => void }) {\n const t = useT()\n const locale = useLocale()\n const canceled = item.status === 'canceled'\n const title = eventDisplayTitle(item.title, t('customers.calendar.grid.untitled', 'Untitled'))\n const timeLabel = item.allDay ? '' : ` \u00B7 ${formatTime(locale, item.start)} \u2013 ${formatTime(locale, item.end)}`\n const tintStyle = item.color\n ? { backgroundColor: `${item.color}${SOFT_TINT_ALPHA}`, color: item.color }\n : undefined\n return (\n <Button\n type=\"button\"\n variant=\"ghost\"\n aria-label={`${title}${timeLabel}`}\n onClick={(event) => {\n event.stopPropagation()\n onItemClick(item)\n }}\n className={cn(\n 'h-4 w-full min-w-0 justify-center gap-0 rounded-full p-0 pr-2',\n !item.color && 'bg-muted text-muted-foreground',\n canceled && 'opacity-60',\n )}\n style={tintStyle}\n >\n <span aria-hidden=\"true\" className=\"flex size-4 shrink-0 items-center justify-center\">\n <span\n className={cn('size-1.5 rounded-full', !item.color && 'bg-muted-foreground')}\n style={item.color ? { backgroundColor: item.color } : undefined}\n />\n </span>\n <span\n className={cn(\n 'min-w-0 truncate text-overline font-medium uppercase tracking-wide',\n canceled && 'line-through',\n )}\n >\n {title}\n </span>\n </Button>\n )\n}\n\nfunction MonthDayCell({\n day,\n anchor,\n items,\n onItemClick,\n onDayOpen,\n}: {\n day: Date\n anchor: Date\n items: CalendarItem[]\n onItemClick: (item: CalendarItem) => void\n onDayOpen: (date: Date) => void\n}) {\n const t = useT()\n const locale = useLocale()\n const inMonth = isSameMonth(day, anchor)\n const today = isToday(day)\n const visibleItems = items.slice(0, MAX_PILLS_PER_DAY)\n const hiddenCount = items.length - visibleItems.length\n const dayLabel = fullDateLabel(locale, day)\n return (\n <div\n className={cn(\n 'relative flex h-full min-w-0 flex-1 flex-col items-start gap-1 overflow-hidden border-r border-border p-2 text-left last:border-r-0',\n isWeekend(day) ? 'bg-muted/40' : 'bg-background',\n )}\n >\n <Button\n type=\"button\"\n variant=\"ghost\"\n aria-label={dayLabel}\n onClick={() => onDayOpen(day)}\n className=\"absolute inset-0 z-0 h-full w-full rounded-none p-0 hover:bg-muted/50\"\n >\n <span className=\"sr-only\">{dayLabel}</span>\n </Button>\n {today ? (\n <span className=\"pointer-events-none relative z-10 flex items-center justify-center rounded-full bg-primary px-1.5 py-0.5 text-xs font-semibold text-primary-foreground\">\n {day.getDate()}\n </span>\n ) : (\n <span className={cn('pointer-events-none relative z-10 text-xs font-medium', inMonth ? 'text-foreground' : 'text-muted-foreground')}>\n {day.getDate()}\n </span>\n )}\n <span className=\"relative z-20 hidden w-full flex-col gap-1 sm:flex\">\n {visibleItems.map((item) => (\n <MonthPill key={item.id} item={item} onItemClick={onItemClick} />\n ))}\n {hiddenCount > 0 ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto self-start p-0 text-overline font-medium text-muted-foreground hover:bg-transparent hover:underline\"\n onClick={(event) => {\n event.stopPropagation()\n onDayOpen(day)\n }}\n >\n {t('customers.calendar.grid.more', '+{count} more', { count: hiddenCount })}\n </Button>\n ) : null}\n </span>\n {items.length > 0 ? (\n <span aria-hidden=\"true\" className=\"pointer-events-none relative z-10 flex w-full flex-wrap items-center gap-0.5 sm:hidden\">\n {items.slice(0, 4).map((item) => (\n <span\n key={item.id}\n className={cn('size-1.5 rounded-full', !item.color && 'bg-muted-foreground')}\n style={item.color ? { backgroundColor: item.color } : undefined}\n />\n ))}\n {items.length > 4 ? (\n <span className=\"text-overline font-medium leading-none text-muted-foreground\">\n +{items.length - 4}\n </span>\n ) : null}\n </span>\n ) : null}\n </div>\n )\n}\n\nexport function MonthGrid({ anchor, items, onItemClick, onDayOpen }: MonthGridProps) {\n const t = useT()\n const locale = useLocale()\n const weeks = React.useMemo(() => buildWeeks(anchor), [anchor])\n const itemsByDay = React.useMemo(() => groupItemsByDay(items), [items])\n return (\n <div className=\"relative flex h-full w-full flex-col overflow-hidden border border-border bg-background\">\n <div className=\"flex h-9 w-full shrink-0 border-b border-border\">\n {weeks[0]?.map((day) => {\n const label = day.toLocaleDateString(locale, { weekday: 'short' }).toLocaleUpperCase(locale)\n return (\n <div\n key={dayKeyOf(day)}\n className=\"flex min-w-0 flex-1 items-center justify-center border-r border-border last:border-r-0 sm:justify-start sm:pl-2.5\"\n >\n <span className=\"truncate text-overline font-medium uppercase tracking-widest text-muted-foreground\">\n <span className=\"sm:hidden\">{label.charAt(0)}</span>\n <span className=\"hidden sm:inline\">{label}</span>\n </span>\n </div>\n )\n })}\n </div>\n {weeks.map((week) => (\n <div\n key={dayKeyOf(week[0])}\n className=\"flex w-full flex-1 border-b border-border last:border-b-0 min-h-14 sm:min-h-[86px]\"\n >\n {week.map((day) => (\n <MonthDayCell\n key={dayKeyOf(day)}\n day={day}\n anchor={anchor}\n items={itemsByDay.get(dayKeyOf(day)) ?? []}\n onItemClick={onItemClick}\n onDayOpen={onDayOpen}\n />\n ))}\n </div>\n ))}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.8-develop.6962.1.bd029a2bb2",
3
+ "version": "0.6.8-develop.6963.1.04a6d320e9",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -254,16 +254,16 @@
254
254
  "zod": "^4.4.3"
255
255
  },
256
256
  "peerDependencies": {
257
- "@open-mercato/ai-assistant": "0.6.8-develop.6962.1.bd029a2bb2",
258
- "@open-mercato/shared": "0.6.8-develop.6962.1.bd029a2bb2",
259
- "@open-mercato/ui": "0.6.8-develop.6962.1.bd029a2bb2",
257
+ "@open-mercato/ai-assistant": "0.6.8-develop.6963.1.04a6d320e9",
258
+ "@open-mercato/shared": "0.6.8-develop.6963.1.04a6d320e9",
259
+ "@open-mercato/ui": "0.6.8-develop.6963.1.04a6d320e9",
260
260
  "react": "^19.0.0",
261
261
  "react-dom": "^19.0.0"
262
262
  },
263
263
  "devDependencies": {
264
- "@open-mercato/ai-assistant": "0.6.8-develop.6962.1.bd029a2bb2",
265
- "@open-mercato/shared": "0.6.8-develop.6962.1.bd029a2bb2",
266
- "@open-mercato/ui": "0.6.8-develop.6962.1.bd029a2bb2",
264
+ "@open-mercato/ai-assistant": "0.6.8-develop.6963.1.04a6d320e9",
265
+ "@open-mercato/shared": "0.6.8-develop.6963.1.04a6d320e9",
266
+ "@open-mercato/ui": "0.6.8-develop.6963.1.04a6d320e9",
267
267
  "@testing-library/dom": "^10.4.1",
268
268
  "@testing-library/jest-dom": "^7.0.0",
269
269
  "@testing-library/react": "^16.3.1",
@@ -44,12 +44,12 @@ function formatUrlHost(location: string): string {
44
44
  }
45
45
  }
46
46
 
47
- function formatTime(date: Date): string {
48
- return date.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' })
47
+ function formatTime(locale: string, date: Date): string {
48
+ return date.toLocaleTimeString(locale, { hour: 'numeric', minute: '2-digit' })
49
49
  }
50
50
 
51
- function groupLabelOf(date: Date): string {
52
- return date.toLocaleDateString(undefined, { weekday: 'long', month: 'short', day: 'numeric' })
51
+ function groupLabelOf(locale: string, date: Date): string {
52
+ return date.toLocaleDateString(locale, { weekday: 'long', month: 'short', day: 'numeric' })
53
53
  }
54
54
 
55
55
  function deriveTypeLabel(interactionType: string): string {
@@ -94,7 +94,7 @@ function AgendaDayHeader({ date, count }: { date: Date; count: number }) {
94
94
  : resolvedCount
95
95
  return (
96
96
  <div className="flex w-full items-center gap-2 bg-muted/50 px-3 py-2.5 sm:px-5">
97
- <span className="text-sm font-semibold text-foreground">{groupLabelOf(date)}</span>
97
+ <span className="text-sm font-semibold text-foreground">{groupLabelOf(locale, date)}</span>
98
98
  {todayMarker || tomorrowMarker ? (
99
99
  <span className={cn('text-xs font-medium', todayMarker ? 'text-foreground' : 'text-muted-foreground')}>
100
100
  {`· ${todayMarker ? t('customers.calendar.agenda.today', 'Today') : t('customers.calendar.agenda.tomorrow', 'Tomorrow')}`}
@@ -116,6 +116,7 @@ function AgendaRow({
116
116
  onItemClick: (item: CalendarItem) => void
117
117
  }) {
118
118
  const t = useT()
119
+ const locale = useLocale()
119
120
  const canceled = item.status === 'canceled'
120
121
  const done = item.status === 'done'
121
122
  const title = eventDisplayTitle(item.title, t('customers.calendar.grid.untitled', 'Untitled'))
@@ -131,8 +132,8 @@ function AgendaRow({
131
132
  : item.locationKind === 'url' && item.location
132
133
  ? formatUrlHost(item.location)
133
134
  : item.location
134
- const startLabel = item.allDay ? t('customers.calendar.grid.allDay', 'All day') : formatTime(item.start)
135
- const endLabel = item.allDay ? null : formatTime(item.end)
135
+ const startLabel = item.allDay ? t('customers.calendar.grid.allDay', 'All day') : formatTime(locale, item.start)
136
+ const endLabel = item.allDay ? null : formatTime(locale, item.end)
136
137
  const ariaTime = item.allDay ? startLabel : `${startLabel} – ${endLabel}`
137
138
  return (
138
139
  <Button
@@ -5,7 +5,7 @@ import { addDays } from 'date-fns/addDays'
5
5
  import { format } from 'date-fns/format'
6
6
  import { isSameMonth } from 'date-fns/isSameMonth'
7
7
  import { isToday } from 'date-fns/isToday'
8
- import { useT } from '@open-mercato/shared/lib/i18n/context'
8
+ import { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'
9
9
  import { cn } from '@open-mercato/shared/lib/utils'
10
10
  import { Button } from '@open-mercato/ui/primitives/button'
11
11
  import { getVisibleRange } from '../../lib/calendar/range'
@@ -19,12 +19,12 @@ function dayKeyOf(date: Date): string {
19
19
  return format(date, 'yyyy-MM-dd')
20
20
  }
21
21
 
22
- function formatTime(date: Date): string {
23
- return date.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' })
22
+ function formatTime(locale: string, date: Date): string {
23
+ return date.toLocaleTimeString(locale, { hour: 'numeric', minute: '2-digit' })
24
24
  }
25
25
 
26
- function fullDateLabel(date: Date): string {
27
- return date.toLocaleDateString(undefined, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })
26
+ function fullDateLabel(locale: string, date: Date): string {
27
+ return date.toLocaleDateString(locale, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })
28
28
  }
29
29
 
30
30
  function isWeekend(date: Date): boolean {
@@ -61,9 +61,10 @@ function groupItemsByDay(items: CalendarItem[]): Map<string, CalendarItem[]> {
61
61
 
62
62
  function MonthPill({ item, onItemClick }: { item: CalendarItem; onItemClick: (item: CalendarItem) => void }) {
63
63
  const t = useT()
64
+ const locale = useLocale()
64
65
  const canceled = item.status === 'canceled'
65
66
  const title = eventDisplayTitle(item.title, t('customers.calendar.grid.untitled', 'Untitled'))
66
- const timeLabel = item.allDay ? '' : ` · ${formatTime(item.start)} – ${formatTime(item.end)}`
67
+ const timeLabel = item.allDay ? '' : ` · ${formatTime(locale, item.start)} – ${formatTime(locale, item.end)}`
67
68
  const tintStyle = item.color
68
69
  ? { backgroundColor: `${item.color}${SOFT_TINT_ALPHA}`, color: item.color }
69
70
  : undefined
@@ -115,11 +116,12 @@ function MonthDayCell({
115
116
  onDayOpen: (date: Date) => void
116
117
  }) {
117
118
  const t = useT()
119
+ const locale = useLocale()
118
120
  const inMonth = isSameMonth(day, anchor)
119
121
  const today = isToday(day)
120
122
  const visibleItems = items.slice(0, MAX_PILLS_PER_DAY)
121
123
  const hiddenCount = items.length - visibleItems.length
122
- const dayLabel = fullDateLabel(day)
124
+ const dayLabel = fullDateLabel(locale, day)
123
125
  return (
124
126
  <div
125
127
  className={cn(
@@ -186,13 +188,14 @@ function MonthDayCell({
186
188
 
187
189
  export function MonthGrid({ anchor, items, onItemClick, onDayOpen }: MonthGridProps) {
188
190
  const t = useT()
191
+ const locale = useLocale()
189
192
  const weeks = React.useMemo(() => buildWeeks(anchor), [anchor])
190
193
  const itemsByDay = React.useMemo(() => groupItemsByDay(items), [items])
191
194
  return (
192
195
  <div className="relative flex h-full w-full flex-col overflow-hidden border border-border bg-background">
193
196
  <div className="flex h-9 w-full shrink-0 border-b border-border">
194
197
  {weeks[0]?.map((day) => {
195
- const label = day.toLocaleDateString(undefined, { weekday: 'short' }).toUpperCase()
198
+ const label = day.toLocaleDateString(locale, { weekday: 'short' }).toLocaleUpperCase(locale)
196
199
  return (
197
200
  <div
198
201
  key={dayKeyOf(day)}