@open-mercato/core 0.5.1-develop.3043.1a796c3920 → 0.6.0

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.
Files changed (106) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +21 -1
  3. package/dist/modules/api_keys/api/keys/route.js +9 -0
  4. package/dist/modules/api_keys/api/keys/route.js.map +2 -2
  5. package/dist/modules/audit_logs/services/accessLogService.js +13 -0
  6. package/dist/modules/audit_logs/services/accessLogService.js.map +3 -3
  7. package/dist/modules/audit_logs/services/actionLogService.js +6 -5
  8. package/dist/modules/audit_logs/services/actionLogService.js.map +2 -2
  9. package/dist/modules/auth/api/roles/acl/route.js +27 -37
  10. package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
  11. package/dist/modules/auth/api/users/route.js +41 -28
  12. package/dist/modules/auth/api/users/route.js.map +3 -3
  13. package/dist/modules/auth/lib/grantChecks.js +160 -0
  14. package/dist/modules/auth/lib/grantChecks.js.map +7 -0
  15. package/dist/modules/configs/cli.js +11 -0
  16. package/dist/modules/configs/cli.js.map +2 -2
  17. package/dist/modules/configs/lib/touchGeneratedBarrels.js +46 -0
  18. package/dist/modules/configs/lib/touchGeneratedBarrels.js.map +7 -0
  19. package/dist/modules/customers/api/activities/route.js +1 -52
  20. package/dist/modules/customers/api/activities/route.js.map +2 -2
  21. package/dist/modules/customers/api/interactions/counts/route.js +2 -1
  22. package/dist/modules/customers/api/interactions/counts/route.js.map +2 -2
  23. package/dist/modules/customers/api/interactions/route.js +21 -1
  24. package/dist/modules/customers/api/interactions/route.js.map +2 -2
  25. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +7 -3
  26. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  27. package/dist/modules/customers/backend/customers/deals/[id]/page.js +5 -1
  28. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  29. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +7 -3
  30. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  31. package/dist/modules/customers/components/detail/ActivitiesCard.js +62 -6
  32. package/dist/modules/customers/components/detail/ActivitiesCard.js.map +2 -2
  33. package/dist/modules/customers/components/detail/ActivitiesDayStrip.js +21 -6
  34. package/dist/modules/customers/components/detail/ActivitiesDayStrip.js.map +2 -2
  35. package/dist/modules/customers/components/detail/ActivitiesSection.js +37 -5
  36. package/dist/modules/customers/components/detail/ActivitiesSection.js.map +2 -2
  37. package/dist/modules/customers/components/detail/ActivityCard.js +69 -17
  38. package/dist/modules/customers/components/detail/ActivityCard.js.map +2 -2
  39. package/dist/modules/customers/components/detail/ActivityHistorySection.js +94 -34
  40. package/dist/modules/customers/components/detail/ActivityHistorySection.js.map +2 -2
  41. package/dist/modules/customers/components/detail/ActivityLogTab.js +3 -1
  42. package/dist/modules/customers/components/detail/ActivityLogTab.js.map +2 -2
  43. package/dist/modules/customers/components/detail/ActivityTimeline.js +41 -8
  44. package/dist/modules/customers/components/detail/ActivityTimeline.js.map +2 -2
  45. package/dist/modules/customers/components/detail/ActivityTimelineFilters.js +19 -6
  46. package/dist/modules/customers/components/detail/ActivityTimelineFilters.js.map +2 -2
  47. package/dist/modules/customers/components/detail/ActivityTypeSelector.js +4 -3
  48. package/dist/modules/customers/components/detail/ActivityTypeSelector.js.map +2 -2
  49. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +80 -12
  50. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  51. package/dist/modules/customers/components/detail/schedule/DateTimeFields.js +65 -10
  52. package/dist/modules/customers/components/detail/schedule/DateTimeFields.js.map +2 -2
  53. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +10 -5
  54. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
  55. package/dist/modules/customers/data/validators.js +74 -2
  56. package/dist/modules/customers/data/validators.js.map +2 -2
  57. package/dist/modules/customers/lib/legacyActivityBridge.js +61 -0
  58. package/dist/modules/customers/lib/legacyActivityBridge.js.map +7 -0
  59. package/dist/modules/integrations/data/validators.js +2 -2
  60. package/dist/modules/integrations/data/validators.js.map +2 -2
  61. package/dist/modules/integrations/lib/credentials-service.js +12 -1
  62. package/dist/modules/integrations/lib/credentials-service.js.map +2 -2
  63. package/dist/modules/messages/commands/actions.js +29 -14
  64. package/dist/modules/messages/commands/actions.js.map +2 -2
  65. package/dist/modules/messages/lib/actions.js +24 -4
  66. package/dist/modules/messages/lib/actions.js.map +2 -2
  67. package/dist/modules/sales/api/documents/factory.js +49 -36
  68. package/dist/modules/sales/api/documents/factory.js.map +2 -2
  69. package/package.json +9 -10
  70. package/src/modules/api_keys/api/keys/route.ts +9 -0
  71. package/src/modules/audit_logs/services/accessLogService.ts +20 -0
  72. package/src/modules/audit_logs/services/actionLogService.ts +13 -5
  73. package/src/modules/auth/api/roles/acl/route.ts +32 -46
  74. package/src/modules/auth/api/users/route.ts +48 -33
  75. package/src/modules/auth/lib/grantChecks.ts +234 -0
  76. package/src/modules/configs/cli.ts +11 -0
  77. package/src/modules/configs/lib/touchGeneratedBarrels.ts +61 -0
  78. package/src/modules/customers/api/activities/route.ts +1 -76
  79. package/src/modules/customers/api/interactions/counts/route.ts +2 -1
  80. package/src/modules/customers/api/interactions/route.ts +28 -1
  81. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +13 -3
  82. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +14 -2
  83. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +13 -3
  84. package/src/modules/customers/components/detail/ActivitiesCard.tsx +92 -5
  85. package/src/modules/customers/components/detail/ActivitiesDayStrip.tsx +38 -6
  86. package/src/modules/customers/components/detail/ActivitiesSection.tsx +37 -3
  87. package/src/modules/customers/components/detail/ActivityCard.tsx +79 -14
  88. package/src/modules/customers/components/detail/ActivityHistorySection.tsx +102 -33
  89. package/src/modules/customers/components/detail/ActivityLogTab.tsx +7 -1
  90. package/src/modules/customers/components/detail/ActivityTimeline.tsx +39 -5
  91. package/src/modules/customers/components/detail/ActivityTimelineFilters.tsx +29 -7
  92. package/src/modules/customers/components/detail/ActivityTypeSelector.tsx +3 -2
  93. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +96 -13
  94. package/src/modules/customers/components/detail/schedule/DateTimeFields.tsx +50 -4
  95. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +21 -5
  96. package/src/modules/customers/data/validators.ts +85 -2
  97. package/src/modules/customers/i18n/de.json +11 -0
  98. package/src/modules/customers/i18n/en.json +11 -0
  99. package/src/modules/customers/i18n/es.json +11 -0
  100. package/src/modules/customers/i18n/pl.json +11 -0
  101. package/src/modules/customers/lib/legacyActivityBridge.ts +106 -0
  102. package/src/modules/integrations/data/validators.ts +8 -6
  103. package/src/modules/integrations/lib/credentials-service.ts +15 -1
  104. package/src/modules/messages/commands/actions.ts +28 -13
  105. package/src/modules/messages/lib/actions.ts +34 -3
  106. package/src/modules/sales/api/documents/factory.ts +55 -38
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/ActivitiesCard.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Calendar, CalendarClock, Clock, Mail, Phone, StickyNote, Users } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { ActivitiesDayStrip } from './ActivitiesDayStrip'\nimport { ActivitiesAddNewMenu, type ActivityKind } from './ActivitiesAddNewMenu'\nimport type { InteractionSummary } from './types'\n\ninterface ActivitiesCardProps {\n entityId: string\n plannedActivities: InteractionSummary[]\n refreshKey?: number\n onAddNew: (kind: ActivityKind) => void\n onEditActivity?: (activity: InteractionSummary) => void\n /**\n * Optional company name for the parent entity. When the planned activity has no `dealTitle`,\n * the row subtitle falls back to \"{type} \u00B7 {company}\" to mirror Figma 784:809.\n */\n entityCompanyName?: string | null\n}\n\nconst TYPE_ICONS: Record<string, React.ComponentType<{ className?: string }>> = {\n call: Phone,\n email: Mail,\n meeting: Users,\n note: StickyNote,\n}\n\nfunction startOfDay(date: Date): Date {\n const next = new Date(date)\n next.setHours(0, 0, 0, 0)\n return next\n}\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()\n}\n\nfunction isOverdue(activity: InteractionSummary, now: Date): boolean {\n const scheduled = activity.scheduledAt ?? activity.occurredAt\n if (!scheduled) return false\n const date = new Date(scheduled)\n if (Number.isNaN(date.getTime())) return false\n return date.getTime() < now.getTime() && activity.status !== 'done'\n}\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })\n}\n\nfunction formatRelativeDay(date: Date, t: TranslateFn): string {\n const now = new Date()\n const today = startOfDay(now)\n const target = startOfDay(date)\n const diff = Math.round((target.getTime() - today.getTime()) / (1000 * 60 * 60 * 24))\n if (diff === 0) return t('customers.timeline.date.today', 'today')\n if (diff === 1) return t('customers.timeline.date.tomorrow', 'tomorrow')\n if (diff === -1) return t('customers.timeline.date.yesterday', 'yesterday')\n return target.toLocaleDateString(undefined, { day: 'numeric', month: 'short' })\n}\n\nfunction formatDuration(minutes: number, t: TranslateFn): string {\n if (minutes >= 60) {\n const hours = Math.round((minutes / 60) * 10) / 10\n return t('customers.activities.calendar.hoursShort', '{hours}h', { hours })\n }\n return t('customers.activities.calendar.minutesShort', '{minutes}m', { minutes })\n}\n\nexport function ActivitiesCard({\n entityId,\n plannedActivities,\n refreshKey = 0,\n onAddNew,\n onEditActivity,\n entityCompanyName,\n}: ActivitiesCardProps) {\n const t = useT()\n const [selectedDate, setSelectedDate] = React.useState<Date>(() => startOfDay(new Date()))\n\n const eventsForSelectedDay = React.useMemo(() => {\n const items = plannedActivities.filter((activity) => {\n const scheduled = activity.scheduledAt ?? activity.occurredAt\n if (!scheduled) return false\n const date = new Date(scheduled)\n if (Number.isNaN(date.getTime())) return false\n return isSameDay(date, selectedDate)\n })\n return items.sort((left, right) => {\n const leftTime = new Date(left.scheduledAt ?? left.occurredAt ?? left.createdAt).getTime()\n const rightTime = new Date(right.scheduledAt ?? right.occurredAt ?? right.createdAt).getTime()\n return leftTime - rightTime\n })\n }, [plannedActivities, selectedDate])\n\n const overdueCount = React.useMemo(() => {\n const now = new Date()\n return plannedActivities.filter((activity) => isOverdue(activity, now)).length\n }, [plannedActivities])\n\n return (\n <div className=\"flex flex-col gap-3 rounded-lg border border-border bg-card pt-4 pb-4 px-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"size-4 text-foreground\" />\n <h3 className=\"text-sm font-semibold leading-none text-foreground\">\n {t('customers.activities.card.title', 'Activities')}\n </h3>\n {overdueCount > 0 ? (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-error-bg px-1.5 py-0.5 text-xs font-medium text-status-error-text\">\n <CalendarClock className=\"size-3\" />\n {t('customers.activities.card.overdue', '{count} overdue', { count: overdueCount })}\n </span>\n ) : null}\n </div>\n <ActivitiesAddNewMenu onSelect={onAddNew} />\n </div>\n\n <ActivitiesDayStrip\n entityId={entityId}\n selectedDate={selectedDate}\n onSelectDate={setSelectedDate}\n refreshKey={refreshKey}\n />\n\n {eventsForSelectedDay.length > 0 ? (\n <>\n <div className=\"h-px w-full bg-border\" />\n <ul className=\"flex flex-col\">\n {eventsForSelectedDay.map((activity) => (\n <PlannedEventRow\n key={activity.id}\n activity={activity}\n onClick={onEditActivity}\n entityCompanyName={entityCompanyName ?? null}\n t={t}\n />\n ))}\n </ul>\n </>\n ) : (\n <>\n <div className=\"h-px w-full bg-border\" />\n <p className=\"px-1 py-2 text-xs text-muted-foreground\">\n {t('customers.activities.card.empty', 'Nothing scheduled for this day.')}\n </p>\n </>\n )}\n </div>\n )\n}\n\ninterface PlannedEventRowProps {\n activity: InteractionSummary\n onClick?: (activity: InteractionSummary) => void\n entityCompanyName: string | null\n t: TranslateFn\n}\n\nfunction PlannedEventRow({ activity, onClick, entityCompanyName, t }: PlannedEventRowProps) {\n const dateStr = activity.scheduledAt ?? activity.occurredAt ?? activity.createdAt\n const date = new Date(dateStr)\n const validDate = !Number.isNaN(date.getTime())\n const Icon = TYPE_ICONS[activity.interactionType] ?? Users\n const duration = typeof activity.duration === 'number' && activity.duration > 0 ? activity.duration : null\n const overdue = validDate && date.getTime() < Date.now() && activity.status !== 'done'\n const typeLabel = labelForType(activity.interactionType, t)\n const subtitleSuffix = activity.dealTitle ?? entityCompanyName ?? null\n const subtitle = subtitleSuffix ? `${typeLabel} \u00B7 ${subtitleSuffix}` : typeLabel\n const interactive = !!onClick\n\n return (\n <li>\n <button\n type=\"button\"\n onClick={interactive ? () => onClick?.(activity) : undefined}\n disabled={!interactive}\n className={cn(\n 'flex w-full items-start gap-[9px] pt-[8px] text-left transition-colors',\n interactive ? 'cursor-pointer rounded-md hover:bg-accent/30 px-1' : 'px-1',\n )}\n >\n <div className=\"flex h-[44px] w-[43px] shrink-0 flex-col gap-[2px] pt-[2px]\">\n <span className=\"text-xs font-semibold leading-none text-foreground\">\n {validDate ? formatTime(date) : ''}\n </span>\n <span className=\"text-[10px] leading-none font-normal text-muted-foreground\">\n {validDate ? formatRelativeDay(date, t) : ''}\n </span>\n </div>\n <div className=\"flex shrink-0 items-center justify-center rounded-full bg-muted border-4 border-background size-7\">\n <Icon className=\"size-4 text-muted-foreground\" />\n </div>\n <div className=\"min-w-0 flex flex-1 flex-col gap-[4px]\">\n <span className=\"text-sm leading-5 tracking-[-0.084px] text-foreground\">\n {activity.title ?? activity.body ?? labelForType(activity.interactionType, t)}\n </span>\n {duration ? (\n <span className={cn(\n 'inline-flex w-fit items-center gap-[2px] rounded-full pl-[4px] pr-[8px] py-[2px] text-xs font-medium leading-[16px]',\n overdue\n ? 'bg-status-error-bg text-status-error-text'\n : 'bg-status-warning-bg text-status-warning-text',\n )}>\n <Clock className=\"size-4\" />\n {formatDuration(duration, t)}\n </span>\n ) : null}\n <span className=\"text-[11px] font-normal text-muted-foreground\">{subtitle}</span>\n </div>\n </button>\n </li>\n )\n}\n\nfunction labelForType(type: string, t: TranslateFn): string {\n const map: Record<string, [string, string]> = {\n meeting: ['customers.timeline.filter.meeting', 'Meeting'],\n call: ['customers.timeline.filter.call', 'Call'],\n email: ['customers.timeline.filter.email', 'Email'],\n note: ['customers.timeline.filter.note', 'Note'],\n task: ['customers.timeline.filter.task', 'Task'],\n }\n const entry = map[type]\n return entry ? t(entry[0], entry[1]) : type\n}\n\nexport default ActivitiesCard\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Calendar, CalendarClock, Clock, Mail, Phone, StickyNote, Users } from 'lucide-react'\nimport { toZonedTime } from 'date-fns-tz'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { ActivitiesDayStrip } from './ActivitiesDayStrip'\nimport { ActivitiesAddNewMenu, type ActivityKind } from './ActivitiesAddNewMenu'\nimport type { InteractionSummary } from './types'\n\ninterface ActivitiesCardProps {\n entityId: string\n /**\n * Initial planned activities (from the parent route's `plannedActivitiesPreview`).\n * Used as the seed value before the broader `/api/customers/interactions` fetch\n * resolves, and as the fallback when the fetch fails. The card always prefers\n * its own fetched window (issue #1809 \u2014 fixes E1 status alignment and E2 type\n * coverage by sourcing from the same endpoint as the day strip rather than the\n * 5-item server preview that excluded most types in practice).\n */\n plannedActivities: InteractionSummary[]\n refreshKey?: number\n onAddNew: (kind: ActivityKind) => void\n onEditActivity?: (activity: InteractionSummary) => void\n /**\n * Optional company name for the parent entity. When the planned activity has no `dealTitle`,\n * the row subtitle falls back to \"{type} \u00B7 {company}\" to mirror Figma 784:809.\n */\n entityCompanyName?: string | null\n}\n\nconst TYPE_ICONS: Record<string, React.ComponentType<{ className?: string }>> = {\n call: Phone,\n email: Mail,\n meeting: Users,\n note: StickyNote,\n}\n\nconst USER_TIMEZONE = (() => {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'\n } catch {\n return 'UTC'\n }\n})()\n\n// Project a UTC instant to the user's local timezone before extracting day/month/year\n// for \"same day\" comparisons (issue #1809 \u2014 E3 timezone drift).\nfunction toLocalZonedDate(value: string | Date): Date {\n return toZonedTime(value, USER_TIMEZONE)\n}\n\nfunction startOfDay(date: Date): Date {\n const next = new Date(date)\n next.setHours(0, 0, 0, 0)\n return next\n}\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()\n}\n\nfunction isOverdue(activity: InteractionSummary, now: Date): boolean {\n const scheduled = activity.scheduledAt ?? activity.occurredAt\n if (!scheduled) return false\n const date = new Date(scheduled)\n if (Number.isNaN(date.getTime())) return false\n return date.getTime() < now.getTime() && activity.status !== 'done'\n}\n\n// Visible window for the day-strip + activity list. Mirrors `VISIBLE_DAYS = 5`\n// in ActivitiesDayStrip with extra padding so navigation forward/back doesn't\n// race the fetch.\nconst FETCH_WINDOW_DAYS = 31\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })\n}\n\nfunction formatRelativeDay(date: Date, t: TranslateFn): string {\n const now = new Date()\n const today = startOfDay(now)\n const target = startOfDay(date)\n const diff = Math.round((target.getTime() - today.getTime()) / (1000 * 60 * 60 * 24))\n if (diff === 0) return t('customers.timeline.date.today', 'today')\n if (diff === 1) return t('customers.timeline.date.tomorrow', 'tomorrow')\n if (diff === -1) return t('customers.timeline.date.yesterday', 'yesterday')\n return target.toLocaleDateString(undefined, { day: 'numeric', month: 'short' })\n}\n\nfunction formatDuration(minutes: number, t: TranslateFn): string {\n if (minutes >= 60) {\n const hours = Math.round((minutes / 60) * 10) / 10\n return t('customers.activities.calendar.hoursShort', '{hours}h', { hours })\n }\n return t('customers.activities.calendar.minutesShort', '{minutes}m', { minutes })\n}\n\nexport function ActivitiesCard({\n entityId,\n plannedActivities,\n refreshKey = 0,\n onAddNew,\n onEditActivity,\n entityCompanyName,\n}: ActivitiesCardProps) {\n const t = useT()\n const [selectedDate, setSelectedDate] = React.useState<Date>(() => startOfDay(new Date()))\n // Fetch the same broader window as the day strip via the canonical interactions\n // endpoint. This single source of truth aligns the day-strip count with the\n // visible event list (issue #1809 \u2014 E1) and surfaces all interaction types\n // (issue #1809 \u2014 E2: the previous reliance on the server-side 5-item preview\n // produced \"Person view shows only Calls\" because the limit happened to drop\n // every non-call entry from the prefix-window).\n const [fetchedEvents, setFetchedEvents] = React.useState<InteractionSummary[] | null>(null)\n\n React.useEffect(() => {\n if (!entityId) {\n setFetchedEvents(null)\n return\n }\n const controller = new AbortController()\n const today = startOfDay(new Date())\n const fromDate = new Date(today)\n fromDate.setDate(today.getDate() - FETCH_WINDOW_DAYS)\n const toDate = new Date(today)\n toDate.setDate(today.getDate() + FETCH_WINDOW_DAYS)\n toDate.setHours(23, 59, 59, 999)\n const params = new URLSearchParams({\n entityId,\n from: fromDate.toISOString(),\n to: toDate.toISOString(),\n // Server caps at 100 (interactions querySchema). 100 is well above what\n // an active CRM record accumulates in a 31-day window of meetings/calls,\n // and the day strip + list naturally degrade to truncation if exceeded.\n limit: '100',\n sortField: 'scheduledAt',\n sortDir: 'asc',\n excludeInteractionType: 'task',\n })\n void (async () => {\n try {\n const payload = await readApiResultOrThrow<{ items?: InteractionSummary[] }>(\n `/api/customers/interactions?${params.toString()}`,\n { signal: controller.signal },\n )\n setFetchedEvents(Array.isArray(payload?.items) ? payload.items : [])\n } catch (err) {\n if ((err as { name?: string } | null)?.name !== 'AbortError') {\n console.warn('[ActivitiesCard] failed to load interactions', err)\n setFetchedEvents(null)\n }\n }\n })()\n return () => controller.abort()\n }, [entityId, refreshKey])\n\n // Prefer the broader fetch when it has resolved; fall back to the seed prop\n // (route-supplied preview) only while the fetch is in flight or after a\n // hard failure. This guarantees that the rare prop-only render path keeps\n // backwards-compat with existing unit tests while live UI uses the broader fetch.\n const effectiveEvents: InteractionSummary[] = fetchedEvents ?? plannedActivities\n\n const eventsForSelectedDay = React.useMemo(() => {\n const items = effectiveEvents.filter((activity) => {\n const scheduled = activity.scheduledAt ?? activity.occurredAt\n if (!scheduled) return false\n const date = new Date(scheduled)\n if (Number.isNaN(date.getTime())) return false\n // Compare in the user's local timezone so a 23:30 local activity stays\n // on its local-day chip instead of bleeding into the next UTC day\n // (issue #1809 \u2014 E3).\n return isSameDay(toLocalZonedDate(scheduled), selectedDate)\n })\n return items.sort((left, right) => {\n const leftTime = new Date(left.scheduledAt ?? left.occurredAt ?? left.createdAt).getTime()\n const rightTime = new Date(right.scheduledAt ?? right.occurredAt ?? right.createdAt).getTime()\n return leftTime - rightTime\n })\n }, [effectiveEvents, selectedDate])\n\n const overdueCount = React.useMemo(() => {\n const now = new Date()\n return effectiveEvents.filter((activity) => isOverdue(activity, now)).length\n }, [effectiveEvents])\n\n return (\n <div className=\"flex flex-col gap-3 rounded-lg border border-border bg-card pt-4 pb-4 px-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <Calendar className=\"size-4 text-foreground\" />\n <h3 className=\"text-sm font-semibold leading-none text-foreground\">\n {t('customers.activities.card.title', 'Activities')}\n </h3>\n {overdueCount > 0 ? (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-error-bg px-1.5 py-0.5 text-xs font-medium text-status-error-text\">\n <CalendarClock className=\"size-3\" />\n {t('customers.activities.card.overdue', '{count} overdue', { count: overdueCount })}\n </span>\n ) : null}\n </div>\n <ActivitiesAddNewMenu onSelect={onAddNew} />\n </div>\n\n <ActivitiesDayStrip\n entityId={entityId}\n selectedDate={selectedDate}\n onSelectDate={setSelectedDate}\n refreshKey={refreshKey}\n events={fetchedEvents ?? undefined}\n />\n\n {eventsForSelectedDay.length > 0 ? (\n <>\n <div className=\"h-px w-full bg-border\" />\n <ul className=\"flex flex-col\">\n {eventsForSelectedDay.map((activity) => (\n <PlannedEventRow\n key={activity.id}\n activity={activity}\n onClick={onEditActivity}\n entityCompanyName={entityCompanyName ?? null}\n t={t}\n />\n ))}\n </ul>\n </>\n ) : (\n <>\n <div className=\"h-px w-full bg-border\" />\n <p className=\"px-1 py-2 text-xs text-muted-foreground\">\n {t('customers.activities.card.empty', 'Nothing scheduled for this day.')}\n </p>\n </>\n )}\n </div>\n )\n}\n\ninterface PlannedEventRowProps {\n activity: InteractionSummary\n onClick?: (activity: InteractionSummary) => void\n entityCompanyName: string | null\n t: TranslateFn\n}\n\nfunction PlannedEventRow({ activity, onClick, entityCompanyName, t }: PlannedEventRowProps) {\n const dateStr = activity.scheduledAt ?? activity.occurredAt ?? activity.createdAt\n const date = new Date(dateStr)\n const validDate = !Number.isNaN(date.getTime())\n const Icon = TYPE_ICONS[activity.interactionType] ?? Users\n const duration = typeof activity.duration === 'number' && activity.duration > 0 ? activity.duration : null\n const overdue = validDate && date.getTime() < Date.now() && activity.status !== 'done'\n const typeLabel = labelForType(activity.interactionType, t)\n const subtitleSuffix = activity.dealTitle ?? entityCompanyName ?? null\n const subtitle = subtitleSuffix ? `${typeLabel} \u00B7 ${subtitleSuffix}` : typeLabel\n const interactive = !!onClick\n\n return (\n <li>\n <button\n type=\"button\"\n onClick={interactive ? () => onClick?.(activity) : undefined}\n disabled={!interactive}\n className={cn(\n 'flex w-full items-start gap-[9px] pt-[8px] text-left transition-colors',\n interactive ? 'cursor-pointer rounded-md hover:bg-accent/30 px-1' : 'px-1',\n )}\n >\n <div className=\"flex h-[44px] w-[43px] shrink-0 flex-col gap-[2px] pt-[2px]\">\n <span className=\"text-xs font-semibold leading-none text-foreground\">\n {validDate ? formatTime(date) : ''}\n </span>\n <span className=\"text-[10px] leading-none font-normal text-muted-foreground\">\n {validDate ? formatRelativeDay(date, t) : ''}\n </span>\n </div>\n <div className=\"flex shrink-0 items-center justify-center rounded-full bg-muted border-4 border-background size-7\">\n <Icon className=\"size-4 text-muted-foreground\" />\n </div>\n <div className=\"min-w-0 flex flex-1 flex-col gap-[4px]\">\n <span className=\"text-sm leading-5 tracking-[-0.084px] text-foreground\">\n {activity.title ?? activity.body ?? labelForType(activity.interactionType, t)}\n </span>\n {duration ? (\n <span className={cn(\n 'inline-flex w-fit items-center gap-[2px] rounded-full pl-[4px] pr-[8px] py-[2px] text-xs font-medium leading-[16px]',\n overdue\n ? 'bg-status-error-bg text-status-error-text'\n : 'bg-status-warning-bg text-status-warning-text',\n )}>\n <Clock className=\"size-4\" />\n {formatDuration(duration, t)}\n </span>\n ) : null}\n <span className=\"text-[11px] font-normal text-muted-foreground\">{subtitle}</span>\n </div>\n </button>\n </li>\n )\n}\n\nfunction labelForType(type: string, t: TranslateFn): string {\n const map: Record<string, [string, string]> = {\n meeting: ['customers.timeline.filter.meeting', 'Meeting'],\n call: ['customers.timeline.filter.call', 'Call'],\n email: ['customers.timeline.filter.email', 'Email'],\n note: ['customers.timeline.filter.note', 'Note'],\n task: ['customers.timeline.filter.task', 'Task'],\n }\n const entry = map[type]\n return entry ? t(entry[0], entry[1]) : type\n}\n\nexport default ActivitiesCard\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,9 +2,20 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { ChevronLeft, ChevronRight } from "lucide-react";
5
+ import { toZonedTime } from "date-fns-tz";
5
6
  import { cn } from "@open-mercato/shared/lib/utils";
6
7
  import { useT } from "@open-mercato/shared/lib/i18n/context";
7
8
  import { readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
9
+ const USER_TIMEZONE = (() => {
10
+ try {
11
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
12
+ } catch {
13
+ return "UTC";
14
+ }
15
+ })();
16
+ function toLocalZonedDate(value) {
17
+ return toZonedTime(value, USER_TIMEZONE);
18
+ }
8
19
  const VISIBLE_DAYS = 5;
9
20
  const BUSYNESS_SLOTS = 10;
10
21
  const SLOT_START_HOUR = 7;
@@ -82,7 +93,8 @@ function computeDayBusyness(events, day) {
82
93
  if (!startIso) continue;
83
94
  const start = new Date(startIso);
84
95
  if (Number.isNaN(start.getTime())) continue;
85
- if (!isSameDay(start, day)) continue;
96
+ const localStart = toLocalZonedDate(startIso);
97
+ if (!isSameDay(localStart, day)) continue;
86
98
  eventCount += 1;
87
99
  const durationMinutes = typeof event.duration === "number" && event.duration > 0 ? event.duration : 30;
88
100
  totalMinutes += durationMinutes;
@@ -127,10 +139,12 @@ function formatDayLabel(date, t) {
127
139
  const entry = DAY_LABEL_KEYS.find(([index]) => index === date.getDay());
128
140
  return entry ? t(entry[1], entry[2]) : "";
129
141
  }
130
- function ActivitiesDayStrip({ entityId, selectedDate, onSelectDate, refreshKey = 0 }) {
142
+ function ActivitiesDayStrip({ entityId, selectedDate, onSelectDate, refreshKey = 0, events: providedEvents }) {
131
143
  const t = useT();
132
144
  const [anchor, setAnchor] = React.useState(() => anchorCenteredOn(selectedDate));
133
- const [events, setEvents] = React.useState([]);
145
+ const [fetchedEvents, setFetchedEvents] = React.useState([]);
146
+ const useProvidedEvents = providedEvents !== void 0;
147
+ const events = useProvidedEvents ? providedEvents : fetchedEvents;
134
148
  React.useEffect(() => {
135
149
  setAnchor((current) => {
136
150
  const days = buildVisibleDays(current);
@@ -142,6 +156,7 @@ function ActivitiesDayStrip({ entityId, selectedDate, onSelectDate, refreshKey =
142
156
  const visibleDays = React.useMemo(() => buildVisibleDays(anchor), [anchor]);
143
157
  const headerLabel = React.useMemo(() => formatMonthLabel(visibleDays[0], t), [visibleDays, t]);
144
158
  React.useEffect(() => {
159
+ if (useProvidedEvents) return;
145
160
  if (!entityId || visibleDays.length === 0) return;
146
161
  const controller = new AbortController();
147
162
  const fromIso = startOfDay(visibleDays[0]).toISOString();
@@ -161,16 +176,16 @@ function ActivitiesDayStrip({ entityId, selectedDate, onSelectDate, refreshKey =
161
176
  `/api/customers/interactions?${params.toString()}`,
162
177
  { signal: controller.signal }
163
178
  );
164
- setEvents(Array.isArray(payload?.items) ? payload.items : []);
179
+ setFetchedEvents(Array.isArray(payload?.items) ? payload.items : []);
165
180
  } catch (err) {
166
181
  if (err?.name !== "AbortError") {
167
182
  console.warn("[ActivitiesDayStrip] failed to load interactions", err);
168
183
  }
169
- setEvents([]);
184
+ setFetchedEvents([]);
170
185
  }
171
186
  })();
172
187
  return () => controller.abort();
173
- }, [entityId, visibleDays, refreshKey]);
188
+ }, [entityId, visibleDays, refreshKey, useProvidedEvents]);
174
189
  const todayDate = React.useMemo(() => startOfDay(/* @__PURE__ */ new Date()), []);
175
190
  const handlePrev = React.useCallback(() => {
176
191
  setAnchor((current) => addDays(current, -VISIBLE_DAYS));
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/ActivitiesDayStrip.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport type { InteractionSummary } from './types'\n\ninterface ActivitiesDayStripProps {\n entityId: string\n selectedDate: Date\n onSelectDate: (date: Date) => void\n refreshKey?: number\n}\n\nconst VISIBLE_DAYS = 5\nconst BUSYNESS_SLOTS = 10\nconst SLOT_START_HOUR = 7\nconst SLOT_END_HOUR = 22\n\nconst DAY_LABEL_KEYS: Array<[number, string, string]> = [\n [0, 'customers.calendar.day.sun', 'SUN'],\n [1, 'customers.calendar.day.mon', 'MON'],\n [2, 'customers.calendar.day.tue', 'TUE'],\n [3, 'customers.calendar.day.wed', 'WED'],\n [4, 'customers.calendar.day.thu', 'THU'],\n [5, 'customers.calendar.day.fri', 'FRI'],\n [6, 'customers.calendar.day.sat', 'SAT'],\n]\n\nconst MONTH_KEYS: Array<[number, string, string]> = [\n [0, 'customers.calendar.month.january', 'January'],\n [1, 'customers.calendar.month.february', 'February'],\n [2, 'customers.calendar.month.march', 'March'],\n [3, 'customers.calendar.month.april', 'April'],\n [4, 'customers.calendar.month.may', 'May'],\n [5, 'customers.calendar.month.june', 'June'],\n [6, 'customers.calendar.month.july', 'July'],\n [7, 'customers.calendar.month.august', 'August'],\n [8, 'customers.calendar.month.september', 'September'],\n [9, 'customers.calendar.month.october', 'October'],\n [10, 'customers.calendar.month.november', 'November'],\n [11, 'customers.calendar.month.december', 'December'],\n]\n\nfunction startOfDay(date: Date): Date {\n const next = new Date(date)\n next.setHours(0, 0, 0, 0)\n return next\n}\n\nfunction endOfDay(date: Date): Date {\n const next = new Date(date)\n next.setHours(23, 59, 59, 999)\n return next\n}\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()\n}\n\nfunction isWeekend(date: Date): boolean {\n const day = date.getDay()\n return day === 0 || day === 6\n}\n\nfunction addDays(date: Date, delta: number): Date {\n const next = new Date(date)\n next.setDate(date.getDate() + delta)\n return next\n}\n\nfunction buildVisibleDays(anchor: Date): Date[] {\n const start = startOfDay(anchor)\n return Array.from({ length: VISIBLE_DAYS }, (_, index) => addDays(start, index))\n}\n\n// Anchor the visible window so that the given focal date lands at the center slot\n// (position 2 out of 5). Matches Figma 784:809 where the selected day is centered.\nfunction anchorCenteredOn(focalDate: Date): Date {\n return startOfDay(addDays(focalDate, -Math.floor(VISIBLE_DAYS / 2)))\n}\n\ntype SlotState = 'empty' | 'partial' | 'full' | 'conflict'\n\ntype DayBusyness = {\n totalMinutes: number\n eventCount: number\n slots: SlotState[]\n}\n\nfunction emptyBusyness(): DayBusyness {\n return {\n totalMinutes: 0,\n eventCount: 0,\n slots: Array<SlotState>(BUSYNESS_SLOTS).fill('empty'),\n }\n}\n\nfunction computeDayBusyness(events: InteractionSummary[], day: Date): DayBusyness {\n if (events.length === 0) return emptyBusyness()\n const dayStart = startOfDay(day).getTime()\n const slotMs = ((SLOT_END_HOUR - SLOT_START_HOUR) * 60 * 60 * 1000) / BUSYNESS_SLOTS\n const slotMinutes = slotMs / 60000\n const slotCounts: number[] = Array(BUSYNESS_SLOTS).fill(0)\n const slotMinutesUsed: number[] = Array(BUSYNESS_SLOTS).fill(0)\n let totalMinutes = 0\n let eventCount = 0\n\n for (const event of events) {\n const startIso = event.scheduledAt ?? event.occurredAt ?? event.createdAt\n if (!startIso) continue\n const start = new Date(startIso)\n if (Number.isNaN(start.getTime())) continue\n if (!isSameDay(start, day)) continue\n eventCount += 1\n const durationMinutes = typeof event.duration === 'number' && event.duration > 0 ? event.duration : 30\n totalMinutes += durationMinutes\n const eventStartMs = start.getTime()\n const eventEndMs = eventStartMs + durationMinutes * 60000\n const slotsStartMs = dayStart + SLOT_START_HOUR * 60 * 60 * 1000\n for (let slot = 0; slot < BUSYNESS_SLOTS; slot += 1) {\n const slotStart = slotsStartMs + slot * slotMs\n const slotEnd = slotStart + slotMs\n const overlapStart = Math.max(slotStart, eventStartMs)\n const overlapEnd = Math.min(slotEnd, eventEndMs)\n const overlapMinutes = Math.max(0, (overlapEnd - overlapStart) / 60000)\n if (overlapMinutes <= 0) continue\n slotCounts[slot] += 1\n slotMinutesUsed[slot] += overlapMinutes\n }\n }\n\n const slots: SlotState[] = slotCounts.map((count, index) => {\n if (count === 0) return 'empty'\n if (count > 1) return 'conflict'\n const used = slotMinutesUsed[index]\n if (used >= slotMinutes * 0.5) return 'full'\n return 'partial'\n })\n\n return { totalMinutes, eventCount, slots }\n}\n\nfunction formatBusyLabel(busy: DayBusyness, t: TranslateFn): string {\n if (busy.eventCount === 0) return ''\n // Match Figma 784:809 label format: \"Xm\" when under an hour, \"Xh\" otherwise.\n // Mixed \"Xh Ym\" overflows the 101px card and is not part of the visual spec.\n const durationLabel = busy.totalMinutes < 60\n ? t('customers.activities.calendar.minutesShort', '{minutes}m', { minutes: Math.max(Math.round(busy.totalMinutes), 1) })\n : t('customers.activities.calendar.hoursShort', '{hours}h', { hours: Math.floor(busy.totalMinutes / 60) })\n return t('customers.activities.calendar.eventsSummary', '{count} {countLabel} \u00B7 {duration}', {\n count: busy.eventCount,\n countLabel: busy.eventCount === 1\n ? t('customers.activities.calendar.eventSingular', 'event')\n : t('customers.activities.calendar.eventPlural', 'events'),\n duration: durationLabel,\n })\n}\n\nfunction formatMonthLabel(date: Date, t: TranslateFn): string {\n const monthEntry = MONTH_KEYS.find(([index]) => index === date.getMonth())\n const monthName = monthEntry ? t(monthEntry[1], monthEntry[2]) : ''\n return t('customers.activities.calendar.monthYear', '{month} {year}', { month: monthName, year: date.getFullYear() })\n}\n\nfunction formatDayLabel(date: Date, t: TranslateFn): string {\n const entry = DAY_LABEL_KEYS.find(([index]) => index === date.getDay())\n return entry ? t(entry[1], entry[2]) : ''\n}\n\nexport function ActivitiesDayStrip({ entityId, selectedDate, onSelectDate, refreshKey = 0 }: ActivitiesDayStripProps) {\n const t = useT()\n const [anchor, setAnchor] = React.useState<Date>(() => anchorCenteredOn(selectedDate))\n const [events, setEvents] = React.useState<InteractionSummary[]>([])\n\n React.useEffect(() => {\n setAnchor((current) => {\n const days = buildVisibleDays(current)\n const visible = days.some((day) => isSameDay(day, selectedDate))\n if (visible) return current\n return anchorCenteredOn(selectedDate)\n })\n }, [selectedDate])\n\n const visibleDays = React.useMemo(() => buildVisibleDays(anchor), [anchor])\n const headerLabel = React.useMemo(() => formatMonthLabel(visibleDays[0], t), [visibleDays, t])\n\n React.useEffect(() => {\n if (!entityId || visibleDays.length === 0) return\n const controller = new AbortController()\n const fromIso = startOfDay(visibleDays[0]).toISOString()\n const toIso = endOfDay(visibleDays[visibleDays.length - 1]).toISOString()\n const params = new URLSearchParams({\n entityId,\n from: fromIso,\n to: toIso,\n limit: '100',\n sortField: 'scheduledAt',\n sortDir: 'asc',\n excludeInteractionType: 'task',\n })\n void (async () => {\n try {\n const payload = await readApiResultOrThrow<{ items?: InteractionSummary[] }>(\n `/api/customers/interactions?${params.toString()}`,\n { signal: controller.signal },\n )\n setEvents(Array.isArray(payload?.items) ? payload.items : [])\n } catch (err) {\n if ((err as { name?: string } | null)?.name !== 'AbortError') {\n console.warn('[ActivitiesDayStrip] failed to load interactions', err)\n }\n setEvents([])\n }\n })()\n return () => controller.abort()\n }, [entityId, visibleDays, refreshKey])\n\n const todayDate = React.useMemo(() => startOfDay(new Date()), [])\n\n const handlePrev = React.useCallback(() => {\n setAnchor((current) => addDays(current, -VISIBLE_DAYS))\n }, [])\n const handleNext = React.useCallback(() => {\n setAnchor((current) => addDays(current, VISIBLE_DAYS))\n }, [])\n const handleHeaderPrev = React.useCallback(() => {\n setAnchor((current) => {\n const next = new Date(current)\n next.setMonth(current.getMonth() - 1)\n return startOfDay(next)\n })\n }, [])\n const handleHeaderNext = React.useCallback(() => {\n setAnchor((current) => {\n const next = new Date(current)\n next.setMonth(current.getMonth() + 1)\n return startOfDay(next)\n })\n }, [])\n\n return (\n <div className=\"flex flex-col gap-2.5 rounded-md px-3.5 py-3 w-full\">\n <div className=\"flex items-center justify-center gap-1.5 rounded-md bg-muted px-1.5 py-1.5\">\n <button\n type=\"button\"\n onClick={handleHeaderPrev}\n aria-label={t('customers.activities.calendar.prevMonth', 'Previous month')}\n className=\"flex size-6 items-center justify-center rounded-md border border-border bg-card shadow-xs hover:bg-accent/40\"\n >\n <ChevronLeft className=\"size-4 text-foreground\" />\n </button>\n <span className=\"flex-1 text-center text-sm font-medium leading-5 text-foreground\">{headerLabel}</span>\n <button\n type=\"button\"\n onClick={handleHeaderNext}\n aria-label={t('customers.activities.calendar.nextMonth', 'Next month')}\n className=\"flex size-6 items-center justify-center rounded-md border border-border bg-card shadow-xs hover:bg-accent/40\"\n >\n <ChevronRight className=\"size-4 text-foreground\" />\n </button>\n </div>\n <div className=\"flex w-full items-center gap-2\">\n <button\n type=\"button\"\n onClick={handlePrev}\n aria-label={t('customers.activities.calendar.prevWindow', 'Previous days')}\n className=\"flex size-6 shrink-0 items-center justify-center rounded-md border border-border bg-card shadow-xs hover:bg-accent/40\"\n >\n <ChevronLeft className=\"size-4 text-foreground\" />\n </button>\n <div className=\"flex flex-1 items-stretch justify-center gap-1\">\n {visibleDays.map((day) => {\n const busy = computeDayBusyness(events, day)\n const isSelected = isSameDay(day, selectedDate)\n const isToday = isSameDay(day, todayDate)\n const weekend = isWeekend(day)\n const busyLabel = busy.eventCount > 0\n ? formatBusyLabel(busy, t)\n : weekend\n ? t('customers.activities.calendar.weekend', 'Weekend')\n : ''\n return (\n <DayCard\n key={day.toISOString()}\n day={day}\n isActive={isSelected}\n isToday={isToday}\n busyness={busy}\n label={busyLabel}\n dayName={formatDayLabel(day, t)}\n onSelect={() => onSelectDate(day)}\n />\n )\n })}\n </div>\n <button\n type=\"button\"\n onClick={handleNext}\n aria-label={t('customers.activities.calendar.nextWindow', 'Next days')}\n className=\"flex size-6 shrink-0 items-center justify-center rounded-md border border-border bg-card shadow-xs hover:bg-accent/40\"\n >\n <ChevronRight className=\"size-4 text-foreground\" />\n </button>\n </div>\n </div>\n )\n}\n\ninterface DayCardProps {\n day: Date\n isActive: boolean\n isToday: boolean\n busyness: DayBusyness\n label: string\n dayName: string\n onSelect: () => void\n}\n\nfunction DayCard({ day, isActive, isToday, busyness, label, dayName, onSelect }: DayCardProps) {\n const dayNumber = String(day.getDate()).padStart(2, '0')\n return (\n <button\n type=\"button\"\n onClick={onSelect}\n aria-pressed={isActive}\n aria-label={`${dayName} ${dayNumber}`}\n className={cn(\n 'flex h-[104px] w-[101px] flex-col items-center gap-[6px] overflow-hidden rounded-[10px] border p-[12px] transition-colors',\n isActive\n ? 'border-transparent bg-foreground'\n : 'border-border bg-card hover:border-foreground/40',\n )}\n >\n <span className=\"text-[11px] font-medium leading-none tracking-[0.44px] text-muted-foreground\">\n {dayName}\n </span>\n <div className=\"flex items-center gap-[5px]\">\n <span\n className={cn(\n 'text-2xl font-semibold leading-7',\n isActive ? 'text-background' : 'text-foreground',\n )}\n >\n {dayNumber}\n </span>\n {isToday ? (\n <span\n className=\"inline-block size-1.5 rounded-full bg-status-info-icon\"\n aria-hidden\n />\n ) : null}\n </div>\n <div className=\"flex h-4 w-[82px] items-end gap-[1.5px]\">\n {busyness.slots.map((state, index) => (\n <BusySlot key={index} state={state} active={isActive} />\n ))}\n </div>\n <span className=\"text-[11px] leading-[14px] font-normal whitespace-nowrap text-muted-foreground\">\n {label}\n </span>\n </button>\n )\n}\n\nfunction BusySlot({ state, active }: { state: SlotState; active: boolean }) {\n const heightClass = state === 'empty'\n ? 'h-0.5'\n : state === 'partial'\n ? 'h-2'\n : 'h-3.5'\n let bgClass: string\n if (state === 'conflict') {\n bgClass = 'bg-status-error-icon'\n } else if (active) {\n if (state === 'empty') bgClass = 'bg-background/30'\n else if (state === 'partial') bgClass = 'bg-background/60'\n else bgClass = 'bg-background'\n } else {\n if (state === 'empty') bgClass = 'bg-border'\n else if (state === 'partial') bgClass = 'bg-muted-foreground'\n else bgClass = 'bg-foreground'\n }\n return <div className={cn('w-[7px] shrink-0 rounded-[1.5px]', heightClass, bgClass)} aria-hidden />\n}\n\nexport default ActivitiesDayStrip\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport { toZonedTime } from 'date-fns-tz'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport type { InteractionSummary } from './types'\n\ninterface ActivitiesDayStripProps {\n entityId: string\n selectedDate: Date\n onSelectDate: (date: Date) => void\n refreshKey?: number\n /**\n * Optional pre-fetched events. When provided, the day strip skips its own fetch\n * and uses the supplied list, ensuring its busyness count agrees with the\n * activity list rendered alongside it (issue #1809 \u2014 E1 status filter alignment).\n */\n events?: InteractionSummary[]\n}\n\nconst USER_TIMEZONE = (() => {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'\n } catch {\n return 'UTC'\n }\n})()\n\n// Project a UTC ISO timestamp to the user's local timezone before comparing\n// \"same day\" (issue #1809 \u2014 E3). The browser's `new Date(iso)` treats the\n// instant correctly, but `getDate()/getMonth()/getFullYear()` reflect the\n// user's local day, so for activities scheduled at e.g. 23:30 local on a UTC\n// boundary the day-strip and list now agree.\nfunction toLocalZonedDate(value: string | Date): Date {\n return toZonedTime(value, USER_TIMEZONE)\n}\n\nconst VISIBLE_DAYS = 5\nconst BUSYNESS_SLOTS = 10\nconst SLOT_START_HOUR = 7\nconst SLOT_END_HOUR = 22\n\nconst DAY_LABEL_KEYS: Array<[number, string, string]> = [\n [0, 'customers.calendar.day.sun', 'SUN'],\n [1, 'customers.calendar.day.mon', 'MON'],\n [2, 'customers.calendar.day.tue', 'TUE'],\n [3, 'customers.calendar.day.wed', 'WED'],\n [4, 'customers.calendar.day.thu', 'THU'],\n [5, 'customers.calendar.day.fri', 'FRI'],\n [6, 'customers.calendar.day.sat', 'SAT'],\n]\n\nconst MONTH_KEYS: Array<[number, string, string]> = [\n [0, 'customers.calendar.month.january', 'January'],\n [1, 'customers.calendar.month.february', 'February'],\n [2, 'customers.calendar.month.march', 'March'],\n [3, 'customers.calendar.month.april', 'April'],\n [4, 'customers.calendar.month.may', 'May'],\n [5, 'customers.calendar.month.june', 'June'],\n [6, 'customers.calendar.month.july', 'July'],\n [7, 'customers.calendar.month.august', 'August'],\n [8, 'customers.calendar.month.september', 'September'],\n [9, 'customers.calendar.month.october', 'October'],\n [10, 'customers.calendar.month.november', 'November'],\n [11, 'customers.calendar.month.december', 'December'],\n]\n\nfunction startOfDay(date: Date): Date {\n const next = new Date(date)\n next.setHours(0, 0, 0, 0)\n return next\n}\n\nfunction endOfDay(date: Date): Date {\n const next = new Date(date)\n next.setHours(23, 59, 59, 999)\n return next\n}\n\nfunction isSameDay(a: Date, b: Date): boolean {\n return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate()\n}\n\nfunction isWeekend(date: Date): boolean {\n const day = date.getDay()\n return day === 0 || day === 6\n}\n\nfunction addDays(date: Date, delta: number): Date {\n const next = new Date(date)\n next.setDate(date.getDate() + delta)\n return next\n}\n\nfunction buildVisibleDays(anchor: Date): Date[] {\n const start = startOfDay(anchor)\n return Array.from({ length: VISIBLE_DAYS }, (_, index) => addDays(start, index))\n}\n\n// Anchor the visible window so that the given focal date lands at the center slot\n// (position 2 out of 5). Matches Figma 784:809 where the selected day is centered.\nfunction anchorCenteredOn(focalDate: Date): Date {\n return startOfDay(addDays(focalDate, -Math.floor(VISIBLE_DAYS / 2)))\n}\n\ntype SlotState = 'empty' | 'partial' | 'full' | 'conflict'\n\ntype DayBusyness = {\n totalMinutes: number\n eventCount: number\n slots: SlotState[]\n}\n\nfunction emptyBusyness(): DayBusyness {\n return {\n totalMinutes: 0,\n eventCount: 0,\n slots: Array<SlotState>(BUSYNESS_SLOTS).fill('empty'),\n }\n}\n\nfunction computeDayBusyness(events: InteractionSummary[], day: Date): DayBusyness {\n if (events.length === 0) return emptyBusyness()\n const dayStart = startOfDay(day).getTime()\n const slotMs = ((SLOT_END_HOUR - SLOT_START_HOUR) * 60 * 60 * 1000) / BUSYNESS_SLOTS\n const slotMinutes = slotMs / 60000\n const slotCounts: number[] = Array(BUSYNESS_SLOTS).fill(0)\n const slotMinutesUsed: number[] = Array(BUSYNESS_SLOTS).fill(0)\n let totalMinutes = 0\n let eventCount = 0\n\n for (const event of events) {\n const startIso = event.scheduledAt ?? event.occurredAt ?? event.createdAt\n if (!startIso) continue\n const start = new Date(startIso)\n if (Number.isNaN(start.getTime())) continue\n // Compare in the user's local timezone so an activity at 23:30 local time\n // doesn't bleed into the next UTC day's chip (issue #1809 \u2014 E3).\n const localStart = toLocalZonedDate(startIso)\n if (!isSameDay(localStart, day)) continue\n eventCount += 1\n const durationMinutes = typeof event.duration === 'number' && event.duration > 0 ? event.duration : 30\n totalMinutes += durationMinutes\n const eventStartMs = start.getTime()\n const eventEndMs = eventStartMs + durationMinutes * 60000\n const slotsStartMs = dayStart + SLOT_START_HOUR * 60 * 60 * 1000\n for (let slot = 0; slot < BUSYNESS_SLOTS; slot += 1) {\n const slotStart = slotsStartMs + slot * slotMs\n const slotEnd = slotStart + slotMs\n const overlapStart = Math.max(slotStart, eventStartMs)\n const overlapEnd = Math.min(slotEnd, eventEndMs)\n const overlapMinutes = Math.max(0, (overlapEnd - overlapStart) / 60000)\n if (overlapMinutes <= 0) continue\n slotCounts[slot] += 1\n slotMinutesUsed[slot] += overlapMinutes\n }\n }\n\n const slots: SlotState[] = slotCounts.map((count, index) => {\n if (count === 0) return 'empty'\n if (count > 1) return 'conflict'\n const used = slotMinutesUsed[index]\n if (used >= slotMinutes * 0.5) return 'full'\n return 'partial'\n })\n\n return { totalMinutes, eventCount, slots }\n}\n\nfunction formatBusyLabel(busy: DayBusyness, t: TranslateFn): string {\n if (busy.eventCount === 0) return ''\n // Match Figma 784:809 label format: \"Xm\" when under an hour, \"Xh\" otherwise.\n // Mixed \"Xh Ym\" overflows the 101px card and is not part of the visual spec.\n const durationLabel = busy.totalMinutes < 60\n ? t('customers.activities.calendar.minutesShort', '{minutes}m', { minutes: Math.max(Math.round(busy.totalMinutes), 1) })\n : t('customers.activities.calendar.hoursShort', '{hours}h', { hours: Math.floor(busy.totalMinutes / 60) })\n return t('customers.activities.calendar.eventsSummary', '{count} {countLabel} \u00B7 {duration}', {\n count: busy.eventCount,\n countLabel: busy.eventCount === 1\n ? t('customers.activities.calendar.eventSingular', 'event')\n : t('customers.activities.calendar.eventPlural', 'events'),\n duration: durationLabel,\n })\n}\n\nfunction formatMonthLabel(date: Date, t: TranslateFn): string {\n const monthEntry = MONTH_KEYS.find(([index]) => index === date.getMonth())\n const monthName = monthEntry ? t(monthEntry[1], monthEntry[2]) : ''\n return t('customers.activities.calendar.monthYear', '{month} {year}', { month: monthName, year: date.getFullYear() })\n}\n\nfunction formatDayLabel(date: Date, t: TranslateFn): string {\n const entry = DAY_LABEL_KEYS.find(([index]) => index === date.getDay())\n return entry ? t(entry[1], entry[2]) : ''\n}\n\nexport function ActivitiesDayStrip({ entityId, selectedDate, onSelectDate, refreshKey = 0, events: providedEvents }: ActivitiesDayStripProps) {\n const t = useT()\n const [anchor, setAnchor] = React.useState<Date>(() => anchorCenteredOn(selectedDate))\n const [fetchedEvents, setFetchedEvents] = React.useState<InteractionSummary[]>([])\n // When the parent supplies `events` (preferred path \u2014 keeps day strip and\n // the list in lockstep, fixes #1809 E1), skip the local fetch entirely.\n const useProvidedEvents = providedEvents !== undefined\n const events = useProvidedEvents ? providedEvents : fetchedEvents\n\n React.useEffect(() => {\n setAnchor((current) => {\n const days = buildVisibleDays(current)\n const visible = days.some((day) => isSameDay(day, selectedDate))\n if (visible) return current\n return anchorCenteredOn(selectedDate)\n })\n }, [selectedDate])\n\n const visibleDays = React.useMemo(() => buildVisibleDays(anchor), [anchor])\n const headerLabel = React.useMemo(() => formatMonthLabel(visibleDays[0], t), [visibleDays, t])\n\n React.useEffect(() => {\n if (useProvidedEvents) return\n if (!entityId || visibleDays.length === 0) return\n const controller = new AbortController()\n const fromIso = startOfDay(visibleDays[0]).toISOString()\n const toIso = endOfDay(visibleDays[visibleDays.length - 1]).toISOString()\n const params = new URLSearchParams({\n entityId,\n from: fromIso,\n to: toIso,\n limit: '100',\n sortField: 'scheduledAt',\n sortDir: 'asc',\n excludeInteractionType: 'task',\n })\n void (async () => {\n try {\n const payload = await readApiResultOrThrow<{ items?: InteractionSummary[] }>(\n `/api/customers/interactions?${params.toString()}`,\n { signal: controller.signal },\n )\n setFetchedEvents(Array.isArray(payload?.items) ? payload.items : [])\n } catch (err) {\n if ((err as { name?: string } | null)?.name !== 'AbortError') {\n console.warn('[ActivitiesDayStrip] failed to load interactions', err)\n }\n setFetchedEvents([])\n }\n })()\n return () => controller.abort()\n }, [entityId, visibleDays, refreshKey, useProvidedEvents])\n\n const todayDate = React.useMemo(() => startOfDay(new Date()), [])\n\n const handlePrev = React.useCallback(() => {\n setAnchor((current) => addDays(current, -VISIBLE_DAYS))\n }, [])\n const handleNext = React.useCallback(() => {\n setAnchor((current) => addDays(current, VISIBLE_DAYS))\n }, [])\n const handleHeaderPrev = React.useCallback(() => {\n setAnchor((current) => {\n const next = new Date(current)\n next.setMonth(current.getMonth() - 1)\n return startOfDay(next)\n })\n }, [])\n const handleHeaderNext = React.useCallback(() => {\n setAnchor((current) => {\n const next = new Date(current)\n next.setMonth(current.getMonth() + 1)\n return startOfDay(next)\n })\n }, [])\n\n return (\n <div className=\"flex flex-col gap-2.5 rounded-md px-3.5 py-3 w-full\">\n <div className=\"flex items-center justify-center gap-1.5 rounded-md bg-muted px-1.5 py-1.5\">\n <button\n type=\"button\"\n onClick={handleHeaderPrev}\n aria-label={t('customers.activities.calendar.prevMonth', 'Previous month')}\n className=\"flex size-6 items-center justify-center rounded-md border border-border bg-card shadow-xs hover:bg-accent/40\"\n >\n <ChevronLeft className=\"size-4 text-foreground\" />\n </button>\n <span className=\"flex-1 text-center text-sm font-medium leading-5 text-foreground\">{headerLabel}</span>\n <button\n type=\"button\"\n onClick={handleHeaderNext}\n aria-label={t('customers.activities.calendar.nextMonth', 'Next month')}\n className=\"flex size-6 items-center justify-center rounded-md border border-border bg-card shadow-xs hover:bg-accent/40\"\n >\n <ChevronRight className=\"size-4 text-foreground\" />\n </button>\n </div>\n <div className=\"flex w-full items-center gap-2\">\n <button\n type=\"button\"\n onClick={handlePrev}\n aria-label={t('customers.activities.calendar.prevWindow', 'Previous days')}\n className=\"flex size-6 shrink-0 items-center justify-center rounded-md border border-border bg-card shadow-xs hover:bg-accent/40\"\n >\n <ChevronLeft className=\"size-4 text-foreground\" />\n </button>\n <div className=\"flex flex-1 items-stretch justify-center gap-1\">\n {visibleDays.map((day) => {\n const busy = computeDayBusyness(events, day)\n const isSelected = isSameDay(day, selectedDate)\n const isToday = isSameDay(day, todayDate)\n const weekend = isWeekend(day)\n const busyLabel = busy.eventCount > 0\n ? formatBusyLabel(busy, t)\n : weekend\n ? t('customers.activities.calendar.weekend', 'Weekend')\n : ''\n return (\n <DayCard\n key={day.toISOString()}\n day={day}\n isActive={isSelected}\n isToday={isToday}\n busyness={busy}\n label={busyLabel}\n dayName={formatDayLabel(day, t)}\n onSelect={() => onSelectDate(day)}\n />\n )\n })}\n </div>\n <button\n type=\"button\"\n onClick={handleNext}\n aria-label={t('customers.activities.calendar.nextWindow', 'Next days')}\n className=\"flex size-6 shrink-0 items-center justify-center rounded-md border border-border bg-card shadow-xs hover:bg-accent/40\"\n >\n <ChevronRight className=\"size-4 text-foreground\" />\n </button>\n </div>\n </div>\n )\n}\n\ninterface DayCardProps {\n day: Date\n isActive: boolean\n isToday: boolean\n busyness: DayBusyness\n label: string\n dayName: string\n onSelect: () => void\n}\n\nfunction DayCard({ day, isActive, isToday, busyness, label, dayName, onSelect }: DayCardProps) {\n const dayNumber = String(day.getDate()).padStart(2, '0')\n return (\n <button\n type=\"button\"\n onClick={onSelect}\n aria-pressed={isActive}\n aria-label={`${dayName} ${dayNumber}`}\n className={cn(\n 'flex h-[104px] w-[101px] flex-col items-center gap-[6px] overflow-hidden rounded-[10px] border p-[12px] transition-colors',\n isActive\n ? 'border-transparent bg-foreground'\n : 'border-border bg-card hover:border-foreground/40',\n )}\n >\n <span className=\"text-[11px] font-medium leading-none tracking-[0.44px] text-muted-foreground\">\n {dayName}\n </span>\n <div className=\"flex items-center gap-[5px]\">\n <span\n className={cn(\n 'text-2xl font-semibold leading-7',\n isActive ? 'text-background' : 'text-foreground',\n )}\n >\n {dayNumber}\n </span>\n {isToday ? (\n <span\n className=\"inline-block size-1.5 rounded-full bg-status-info-icon\"\n aria-hidden\n />\n ) : null}\n </div>\n <div className=\"flex h-4 w-[82px] items-end gap-[1.5px]\">\n {busyness.slots.map((state, index) => (\n <BusySlot key={index} state={state} active={isActive} />\n ))}\n </div>\n <span className=\"text-[11px] leading-[14px] font-normal whitespace-nowrap text-muted-foreground\">\n {label}\n </span>\n </button>\n )\n}\n\nfunction BusySlot({ state, active }: { state: SlotState; active: boolean }) {\n const heightClass = state === 'empty'\n ? 'h-0.5'\n : state === 'partial'\n ? 'h-2'\n : 'h-3.5'\n let bgClass: string\n if (state === 'conflict') {\n bgClass = 'bg-status-error-icon'\n } else if (active) {\n if (state === 'empty') bgClass = 'bg-background/30'\n else if (state === 'partial') bgClass = 'bg-background/60'\n else bgClass = 'bg-background'\n } else {\n if (state === 'empty') bgClass = 'bg-border'\n else if (state === 'partial') bgClass = 'bg-muted-foreground'\n else bgClass = 'bg-foreground'\n }\n return <div className={cn('w-[7px] shrink-0 rounded-[1.5px]', heightClass, bgClass)} aria-hidden />\n}\n\nexport default ActivitiesDayStrip\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -3,7 +3,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Clock, Search } from "lucide-react";
5
5
  import { useT } from "@open-mercato/shared/lib/i18n/context";
6
- import { readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
6
+ import { apiCallOrThrow, readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
7
7
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
8
8
  import { Button } from "@open-mercato/ui/primitives/button";
9
9
  import { Kbd } from "@open-mercato/ui/primitives/kbd";
@@ -72,7 +72,8 @@ function ActivitiesSection({
72
72
  onActionChange,
73
73
  onLoadingChange,
74
74
  refreshKey = 0,
75
- onEditActivity
75
+ onEditActivity,
76
+ runGuardedMutation
76
77
  }) {
77
78
  const t = useT();
78
79
  const [filterTypes, setFilterTypes] = React.useState([]);
@@ -123,13 +124,14 @@ function ActivitiesSection({
123
124
  }
124
125
  setLoading(true);
125
126
  try {
127
+ const taskFilterActive = filterTypes.includes("task");
126
128
  const canonicalParams = new URLSearchParams({
127
129
  entityId,
128
130
  limit: "50",
129
131
  sortField: "occurredAt",
130
- sortDir: "desc",
131
- excludeInteractionType: "task"
132
+ sortDir: "desc"
132
133
  });
134
+ if (!taskFilterActive) canonicalParams.set("excludeInteractionType", "task");
133
135
  if (dealId) canonicalParams.set("dealId", dealId);
134
136
  if (filterTypes.length > 0) canonicalParams.set("type", filterTypes.join(","));
135
137
  if (filterDateFrom) canonicalParams.set("from", filterDateFrom);
@@ -200,6 +202,29 @@ function ActivitiesSection({
200
202
  React.useEffect(() => {
201
203
  setLoadedPages(1);
202
204
  }, [dealId, entityId, filterDateFrom, filterDateTo, filterTypes, useCanonicalInteractions]);
205
+ const handleMarkDone = React.useCallback(async (activityId) => {
206
+ try {
207
+ const operation = () => apiCallOrThrow("/api/customers/interactions/complete", {
208
+ method: "POST",
209
+ headers: { "content-type": "application/json" },
210
+ body: JSON.stringify({ id: activityId, occurredAt: (/* @__PURE__ */ new Date()).toISOString() })
211
+ });
212
+ if (runGuardedMutation) {
213
+ await runGuardedMutation(operation, {
214
+ id: activityId,
215
+ status: "done",
216
+ operation: "completeActivity"
217
+ });
218
+ } else {
219
+ await operation();
220
+ }
221
+ flash(t("customers.activities.actions.markDoneSuccess", "Activity marked done"), "success");
222
+ await loadActivities();
223
+ } catch (err) {
224
+ console.warn("[customers.activitiesSection] mark done failed", activityId, err);
225
+ flash(t("customers.activities.actions.markDoneError", "Could not mark activity as done"), "error");
226
+ }
227
+ }, [loadActivities, runGuardedMutation, t]);
203
228
  const resolvedUserIdsRef = React.useRef(/* @__PURE__ */ new Set());
204
229
  React.useEffect(() => {
205
230
  loadActivities().then(() => {
@@ -281,7 +306,14 @@ function ActivitiesSection({
281
306
  }
282
307
  ),
283
308
  loading && totalCount === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-dashed border-border/70 px-4 py-8 text-sm text-muted-foreground", children: t("customers.people.detail.activities.loading", "Loading activities\u2026") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
284
- /* @__PURE__ */ jsx(ActivityTimeline, { activities: visibleActivities, onEdit: onEditActivity }),
309
+ /* @__PURE__ */ jsx(
310
+ ActivityTimeline,
311
+ {
312
+ activities: visibleActivities,
313
+ onEdit: onEditActivity,
314
+ onMarkDone: handleMarkDone
315
+ }
316
+ ),
285
317
  totalCount > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-t border-border/60 pt-3", children: [
286
318
  /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: searchTerm.trim() ? t("customers.activities.seeMatching", "Showing {visible} of {total} activities", {
287
319
  visible: visibleCount,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/ActivitiesSection.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Clock, Search } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport type { SectionAction, TabEmptyStateConfig } from '@open-mercato/ui/backend/detail'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Kbd } from '@open-mercato/ui/primitives/kbd'\nimport { ActivityTimelineFilters } from './ActivityTimelineFilters'\nimport { ActivityTimeline } from './ActivityTimeline'\nimport type { ActivitySummary, InteractionSummary } from './types'\n\ntype GuardedMutationRunner = <T>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\nexport type ActivitiesSectionProps = {\n entityId: string | null\n entityName?: string | null\n dealId?: string | null\n useCanonicalInteractions?: boolean\n addActionLabel: string\n emptyState: TabEmptyStateConfig\n onActionChange?: (action: SectionAction | null) => void\n onLoadingChange?: (isLoading: boolean) => void\n onDataRefresh?: () => void\n dealOptions?: Array<{ id: string; label: string }>\n entityOptions?: Array<{ id: string; label: string }>\n defaultEntityId?: string | null\n runGuardedMutation?: GuardedMutationRunner\n refreshKey?: number\n onEditActivity?: (activity: InteractionSummary) => void\n}\n\nfunction toDateOnly(value: string | null | undefined): string {\n if (!value) return ''\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? '' : date.toISOString().slice(0, 10)\n}\n\nfunction normalizeLegacyActivity(activity: ActivitySummary): InteractionSummary {\n return {\n id: activity.id,\n interactionType: activity.activityType,\n title: activity.subject ?? null,\n body: activity.body ?? null,\n status: 'done',\n scheduledAt: null,\n occurredAt: activity.occurredAt ?? null,\n priority: null,\n authorUserId: activity.authorUserId ?? null,\n ownerUserId: null,\n appearanceIcon: activity.appearanceIcon ?? null,\n appearanceColor: activity.appearanceColor ?? null,\n source: 'legacy-activity',\n entityId: activity.entityId ?? null,\n dealId: activity.dealId ?? null,\n organizationId: null,\n tenantId: null,\n authorName: activity.authorName ?? null,\n authorEmail: activity.authorEmail ?? null,\n dealTitle: activity.dealTitle ?? null,\n customValues: activity.customValues ?? null,\n createdAt: activity.createdAt,\n updatedAt: activity.createdAt,\n }\n}\n\nfunction sortTimelineActivities(items: InteractionSummary[]): InteractionSummary[] {\n const now = Date.now()\n return [...items].sort((left, right) => {\n const leftScheduled = left.scheduledAt ? new Date(left.scheduledAt).getTime() : Number.NaN\n const rightScheduled = right.scheduledAt ? new Date(right.scheduledAt).getTime() : Number.NaN\n const leftIsPlanned = left.status === 'planned' && Number.isFinite(leftScheduled)\n const rightIsPlanned = right.status === 'planned' && Number.isFinite(rightScheduled)\n const leftIsUpcoming = leftIsPlanned && leftScheduled >= now\n const rightIsUpcoming = rightIsPlanned && rightScheduled >= now\n\n if (leftIsUpcoming !== rightIsUpcoming) {\n return leftIsUpcoming ? -1 : 1\n }\n\n if (leftIsUpcoming && rightIsUpcoming) {\n if (leftScheduled === rightScheduled) return left.id.localeCompare(right.id)\n return leftScheduled - rightScheduled\n }\n\n const leftTime = left.occurredAt ?? left.createdAt\n const rightTime = right.occurredAt ?? right.createdAt\n const compare = rightTime.localeCompare(leftTime)\n if (compare !== 0) return compare\n return right.id.localeCompare(left.id)\n })\n}\n\nexport function ActivitiesSection({\n entityId,\n entityName,\n dealId,\n useCanonicalInteractions = false,\n onActionChange,\n onLoadingChange,\n refreshKey = 0,\n onEditActivity,\n}: ActivitiesSectionProps) {\n const t = useT()\n const [filterTypes, setFilterTypes] = React.useState<string[]>([])\n const [filterDateFrom, setFilterDateFrom] = React.useState('')\n const [filterDateTo, setFilterDateTo] = React.useState('')\n const [searchTerm, setSearchTerm] = React.useState('')\n const [activities, setActivities] = React.useState<InteractionSummary[]>([])\n const [loading, setLoading] = React.useState(false)\n const [hasMore, setHasMore] = React.useState(false)\n const [loadedPages, setLoadedPages] = React.useState(1)\n const searchInputRef = React.useRef<HTMLInputElement>(null)\n\n React.useEffect(() => {\n if (!entityId) return\n function handleShortcut(event: KeyboardEvent) {\n if ((event.metaKey || event.ctrlKey) && event.key === '1') {\n event.preventDefault()\n searchInputRef.current?.focus()\n }\n }\n window.addEventListener('keydown', handleShortcut)\n return () => window.removeEventListener('keydown', handleShortcut)\n }, [entityId])\n\n const visibleActivities = React.useMemo(() => {\n const term = searchTerm.trim().toLowerCase()\n if (!term) return activities\n return activities.filter((activity) => {\n const haystack = [\n activity.title,\n activity.body,\n activity.authorName,\n activity.dealTitle,\n activity.interactionType,\n ]\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n .join(' ')\n .toLowerCase()\n return haystack.includes(term)\n })\n }, [activities, searchTerm])\n\n React.useEffect(() => {\n onActionChange?.(null)\n return () => onActionChange?.(null)\n }, [onActionChange])\n\n React.useEffect(() => {\n onLoadingChange?.(loading)\n }, [loading, onLoadingChange])\n\n const loadActivities = React.useCallback(async () => {\n if (!entityId) {\n setActivities([])\n return\n }\n\n setLoading(true)\n try {\n // Always fetch canonical interactions (new activities are always created here)\n const canonicalParams = new URLSearchParams({\n entityId,\n limit: '50',\n sortField: 'occurredAt',\n sortDir: 'desc',\n excludeInteractionType: 'task',\n })\n if (dealId) canonicalParams.set('dealId', dealId)\n if (filterTypes.length > 0) canonicalParams.set('type', filterTypes.join(','))\n if (filterDateFrom) canonicalParams.set('from', filterDateFrom)\n if (filterDateTo) canonicalParams.set('to', filterDateTo)\n\n const canonicalItems: InteractionSummary[] = []\n let canonicalCursor: string | undefined\n let canonicalHasMore = false\n let pageIndex = 0\n do {\n const params = new URLSearchParams(canonicalParams)\n if (canonicalCursor) params.set('cursor', canonicalCursor)\n const canonicalPayload = await readApiResultOrThrow<{ items?: InteractionSummary[]; nextCursor?: string }>(\n `/api/customers/interactions?${params.toString()}`,\n ).catch(() => ({ items: [] as InteractionSummary[], nextCursor: undefined }))\n canonicalItems.push(...(Array.isArray(canonicalPayload?.items) ? canonicalPayload.items : []))\n canonicalCursor = typeof canonicalPayload?.nextCursor === 'string' ? canonicalPayload.nextCursor : undefined\n canonicalHasMore = Boolean(canonicalCursor)\n pageIndex += 1\n } while (canonicalCursor && pageIndex < loadedPages)\n\n if (useCanonicalInteractions) {\n setActivities(sortTimelineActivities(canonicalItems))\n setHasMore(canonicalHasMore)\n return\n }\n\n // In legacy mode, also fetch legacy activities and merge with canonical\n const legacyItems: InteractionSummary[] = []\n let legacyTotalPages = 1\n for (let legacyPage = 1; legacyPage <= loadedPages; legacyPage += 1) {\n const legacyParams = new URLSearchParams({\n entityId,\n page: String(legacyPage),\n pageSize: '50',\n sortField: 'occurredAt',\n sortDir: 'desc',\n })\n if (dealId) legacyParams.set('dealId', dealId)\n const legacyPayload = await readApiResultOrThrow<{ items?: ActivitySummary[]; totalPages?: number }>(\n `/api/customers/activities?${legacyParams.toString()}`,\n ).catch(() => ({ items: [] as ActivitySummary[], totalPages: 1 }))\n legacyItems.push(...(Array.isArray(legacyPayload?.items) ? legacyPayload.items.map(normalizeLegacyActivity) : []))\n legacyTotalPages = typeof legacyPayload?.totalPages === 'number' ? legacyPayload.totalPages : legacyTotalPages\n }\n const legacyFiltered = legacyItems.filter((entry) => {\n if (filterTypes.length > 0 && !filterTypes.includes(entry.interactionType)) return false\n const dateOnly = toDateOnly(entry.occurredAt ?? entry.createdAt)\n if (filterDateFrom && dateOnly < filterDateFrom) return false\n if (filterDateTo && dateOnly > filterDateTo) return false\n return true\n })\n\n // Merge and deduplicate by id, sort newest first\n const seen = new Set<string>()\n const merged: InteractionSummary[] = []\n for (const item of [...canonicalItems, ...legacyFiltered]) {\n if (!seen.has(item.id)) {\n seen.add(item.id)\n merged.push(item)\n }\n }\n setActivities(sortTimelineActivities(merged))\n setHasMore(canonicalHasMore || legacyTotalPages > loadedPages)\n } catch (error) {\n console.error('customers.activities.history failed', error)\n flash(t('customers.activities.loadFailed', 'Failed to load activities.'), 'error')\n setActivities([])\n setHasMore(false)\n } finally {\n setLoading(false)\n }\n }, [dealId, entityId, filterDateFrom, filterDateTo, filterTypes, loadedPages, useCanonicalInteractions, refreshKey, t])\n\n React.useEffect(() => {\n setLoadedPages(1)\n }, [dealId, entityId, filterDateFrom, filterDateTo, filterTypes, useCanonicalInteractions])\n\n const resolvedUserIdsRef = React.useRef(new Set<string>())\n\n // Resolve missing author names from user IDs\n React.useEffect(() => {\n loadActivities()\n .then(() => { resolvedUserIdsRef.current = new Set() })\n .catch((err) => console.warn('[ActivitiesSection] loadActivities failed', err))\n }, [loadActivities])\n\n React.useEffect(() => {\n const unresolvedIds = new Set<string>()\n for (const a of activities) {\n if (a.authorUserId && !a.authorName && !resolvedUserIdsRef.current.has(a.authorUserId)) {\n unresolvedIds.add(a.authorUserId)\n }\n }\n if (unresolvedIds.size === 0) return\n\n for (const uid of unresolvedIds) resolvedUserIdsRef.current.add(uid)\n\n const controller = new AbortController()\n readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n `/api/auth/users?ids=${[...unresolvedIds].join(',')}`,\n { signal: controller.signal },\n )\n .then((data) => {\n const users = Array.isArray(data?.items) ? data.items : []\n const nameMap = new Map<string, string>()\n for (const user of users) {\n const userId = typeof user.id === 'string' ? user.id : null\n const name = typeof user.display_name === 'string' && user.display_name.trim()\n ? user.display_name.trim()\n : typeof user.email === 'string'\n ? user.email\n : null\n if (userId && name) nameMap.set(userId, name)\n }\n if (nameMap.size > 0) {\n setActivities((prev) =>\n prev.map((a) => {\n if (a.authorUserId && !a.authorName && nameMap.has(a.authorUserId)) {\n return { ...a, authorName: nameMap.get(a.authorUserId) ?? null }\n }\n return a\n }),\n )\n }\n })\n .catch((err) => console.warn('[ActivitiesSection] resolve author names failed', err))\n return () => controller.abort()\n }, [activities])\n\n const totalCount = activities.length\n const visibleCount = visibleActivities.length\n\n return (\n <div className=\"flex flex-col gap-3.5 rounded-[10px] border border-border bg-card pt-4 pb-[18px] px-[18px]\">\n <div className=\"flex items-center gap-2\">\n <Clock className=\"size-[15px] text-muted-foreground\" />\n <h3 className=\"text-[13px] font-semibold text-foreground\">\n {entityName\n ? t('customers.timeline.history.title', 'Interaction history with {{name}}', { name: entityName })\n : t('customers.timeline.history.titleGeneric', 'Interaction history')}\n </h3>\n </div>\n\n <label className=\"relative flex items-center\">\n <Search className=\"pointer-events-none absolute left-2.5 size-5 text-muted-foreground\" aria-hidden />\n <input\n ref={searchInputRef}\n type=\"search\"\n value={searchTerm}\n onChange={(event) => setSearchTerm(event.target.value)}\n placeholder={t('customers.timeline.history.searchPlaceholder', 'Search...')}\n aria-label={t('customers.timeline.history.searchAriaLabel', 'Search interaction history')}\n className=\"h-9 w-full rounded-[10px] border border-border bg-card pl-9 pr-14 text-sm text-foreground shadow-xs placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring [&::-webkit-search-cancel-button]:hidden [&::-webkit-search-decoration]:hidden\"\n />\n <Kbd className=\"pointer-events-none absolute right-2 hidden text-[11px] uppercase tracking-[0.48px] sm:inline-flex\">\n \u23181\n </Kbd>\n </label>\n\n <ActivityTimelineFilters\n entityId={entityId}\n activeTypes={filterTypes}\n dateFrom={filterDateFrom}\n dateTo={filterDateTo}\n onTypesChange={setFilterTypes}\n onDateFromChange={setFilterDateFrom}\n onDateToChange={setFilterDateTo}\n onReset={() => {\n setFilterTypes([])\n setFilterDateFrom('')\n setFilterDateTo('')\n }}\n />\n\n {loading && totalCount === 0 ? (\n <div className=\"rounded-lg border border-dashed border-border/70 px-4 py-8 text-sm text-muted-foreground\">\n {t('customers.people.detail.activities.loading', 'Loading activities\u2026')}\n </div>\n ) : (\n <>\n <ActivityTimeline activities={visibleActivities} onEdit={onEditActivity} />\n {totalCount > 0 ? (\n <div className=\"flex items-center justify-between gap-3 border-t border-border/60 pt-3\">\n <span className=\"text-xs text-muted-foreground\">\n {searchTerm.trim()\n ? t('customers.activities.seeMatching', 'Showing {visible} of {total} activities', {\n visible: visibleCount,\n total: totalCount,\n })\n : t('customers.activities.seeAll', 'See all {count} activities', { count: totalCount })}\n </span>\n {hasMore ? (\n <Button type=\"button\" variant=\"link\" size=\"sm\" onClick={() => setLoadedPages((value) => value + 1)}>\n {t('customers.activities.loadMore', 'Load more')}\n </Button>\n ) : null}\n </div>\n ) : null}\n </>\n )}\n </div>\n )\n}\n\nexport default ActivitiesSection\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Clock, Search } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport type { SectionAction, TabEmptyStateConfig } from '@open-mercato/ui/backend/detail'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Kbd } from '@open-mercato/ui/primitives/kbd'\nimport { ActivityTimelineFilters } from './ActivityTimelineFilters'\nimport { ActivityTimeline } from './ActivityTimeline'\nimport type { ActivitySummary, InteractionSummary } from './types'\n\ntype GuardedMutationRunner = <T>(\n operation: () => Promise<T>,\n mutationPayload?: Record<string, unknown>,\n) => Promise<T>\n\nexport type ActivitiesSectionProps = {\n entityId: string | null\n entityName?: string | null\n dealId?: string | null\n useCanonicalInteractions?: boolean\n addActionLabel: string\n emptyState: TabEmptyStateConfig\n onActionChange?: (action: SectionAction | null) => void\n onLoadingChange?: (isLoading: boolean) => void\n onDataRefresh?: () => void\n dealOptions?: Array<{ id: string; label: string }>\n entityOptions?: Array<{ id: string; label: string }>\n defaultEntityId?: string | null\n runGuardedMutation?: GuardedMutationRunner\n refreshKey?: number\n onEditActivity?: (activity: InteractionSummary) => void\n}\n\nfunction toDateOnly(value: string | null | undefined): string {\n if (!value) return ''\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? '' : date.toISOString().slice(0, 10)\n}\n\nfunction normalizeLegacyActivity(activity: ActivitySummary): InteractionSummary {\n return {\n id: activity.id,\n interactionType: activity.activityType,\n title: activity.subject ?? null,\n body: activity.body ?? null,\n status: 'done',\n scheduledAt: null,\n occurredAt: activity.occurredAt ?? null,\n priority: null,\n authorUserId: activity.authorUserId ?? null,\n ownerUserId: null,\n appearanceIcon: activity.appearanceIcon ?? null,\n appearanceColor: activity.appearanceColor ?? null,\n source: 'legacy-activity',\n entityId: activity.entityId ?? null,\n dealId: activity.dealId ?? null,\n organizationId: null,\n tenantId: null,\n authorName: activity.authorName ?? null,\n authorEmail: activity.authorEmail ?? null,\n dealTitle: activity.dealTitle ?? null,\n customValues: activity.customValues ?? null,\n createdAt: activity.createdAt,\n updatedAt: activity.createdAt,\n }\n}\n\nfunction sortTimelineActivities(items: InteractionSummary[]): InteractionSummary[] {\n const now = Date.now()\n return [...items].sort((left, right) => {\n const leftScheduled = left.scheduledAt ? new Date(left.scheduledAt).getTime() : Number.NaN\n const rightScheduled = right.scheduledAt ? new Date(right.scheduledAt).getTime() : Number.NaN\n const leftIsPlanned = left.status === 'planned' && Number.isFinite(leftScheduled)\n const rightIsPlanned = right.status === 'planned' && Number.isFinite(rightScheduled)\n const leftIsUpcoming = leftIsPlanned && leftScheduled >= now\n const rightIsUpcoming = rightIsPlanned && rightScheduled >= now\n\n if (leftIsUpcoming !== rightIsUpcoming) {\n return leftIsUpcoming ? -1 : 1\n }\n\n if (leftIsUpcoming && rightIsUpcoming) {\n if (leftScheduled === rightScheduled) return left.id.localeCompare(right.id)\n return leftScheduled - rightScheduled\n }\n\n const leftTime = left.occurredAt ?? left.createdAt\n const rightTime = right.occurredAt ?? right.createdAt\n const compare = rightTime.localeCompare(leftTime)\n if (compare !== 0) return compare\n return right.id.localeCompare(left.id)\n })\n}\n\nexport function ActivitiesSection({\n entityId,\n entityName,\n dealId,\n useCanonicalInteractions = false,\n onActionChange,\n onLoadingChange,\n refreshKey = 0,\n onEditActivity,\n runGuardedMutation,\n}: ActivitiesSectionProps) {\n const t = useT()\n const [filterTypes, setFilterTypes] = React.useState<string[]>([])\n const [filterDateFrom, setFilterDateFrom] = React.useState('')\n const [filterDateTo, setFilterDateTo] = React.useState('')\n const [searchTerm, setSearchTerm] = React.useState('')\n const [activities, setActivities] = React.useState<InteractionSummary[]>([])\n const [loading, setLoading] = React.useState(false)\n const [hasMore, setHasMore] = React.useState(false)\n const [loadedPages, setLoadedPages] = React.useState(1)\n const searchInputRef = React.useRef<HTMLInputElement>(null)\n\n React.useEffect(() => {\n if (!entityId) return\n function handleShortcut(event: KeyboardEvent) {\n if ((event.metaKey || event.ctrlKey) && event.key === '1') {\n event.preventDefault()\n searchInputRef.current?.focus()\n }\n }\n window.addEventListener('keydown', handleShortcut)\n return () => window.removeEventListener('keydown', handleShortcut)\n }, [entityId])\n\n const visibleActivities = React.useMemo(() => {\n const term = searchTerm.trim().toLowerCase()\n if (!term) return activities\n return activities.filter((activity) => {\n const haystack = [\n activity.title,\n activity.body,\n activity.authorName,\n activity.dealTitle,\n activity.interactionType,\n ]\n .filter((value): value is string => typeof value === 'string' && value.length > 0)\n .join(' ')\n .toLowerCase()\n return haystack.includes(term)\n })\n }, [activities, searchTerm])\n\n React.useEffect(() => {\n onActionChange?.(null)\n return () => onActionChange?.(null)\n }, [onActionChange])\n\n React.useEffect(() => {\n onLoadingChange?.(loading)\n }, [loading, onLoadingChange])\n\n const loadActivities = React.useCallback(async () => {\n if (!entityId) {\n setActivities([])\n return\n }\n\n setLoading(true)\n try {\n // Always fetch canonical interactions (new activities are always created here)\n const taskFilterActive = filterTypes.includes('task')\n const canonicalParams = new URLSearchParams({\n entityId,\n limit: '50',\n sortField: 'occurredAt',\n sortDir: 'desc',\n })\n // Hide tasks from the activity timeline by default \u2014 they have their own tab \u2014\n // but lift the exclusion when the user explicitly toggled the Task chip on\n // (mirrors `ActivityHistorySection.tsx` after the #1805 fix).\n if (!taskFilterActive) canonicalParams.set('excludeInteractionType', 'task')\n if (dealId) canonicalParams.set('dealId', dealId)\n if (filterTypes.length > 0) canonicalParams.set('type', filterTypes.join(','))\n if (filterDateFrom) canonicalParams.set('from', filterDateFrom)\n if (filterDateTo) canonicalParams.set('to', filterDateTo)\n\n const canonicalItems: InteractionSummary[] = []\n let canonicalCursor: string | undefined\n let canonicalHasMore = false\n let pageIndex = 0\n do {\n const params = new URLSearchParams(canonicalParams)\n if (canonicalCursor) params.set('cursor', canonicalCursor)\n const canonicalPayload = await readApiResultOrThrow<{ items?: InteractionSummary[]; nextCursor?: string }>(\n `/api/customers/interactions?${params.toString()}`,\n ).catch(() => ({ items: [] as InteractionSummary[], nextCursor: undefined }))\n canonicalItems.push(...(Array.isArray(canonicalPayload?.items) ? canonicalPayload.items : []))\n canonicalCursor = typeof canonicalPayload?.nextCursor === 'string' ? canonicalPayload.nextCursor : undefined\n canonicalHasMore = Boolean(canonicalCursor)\n pageIndex += 1\n } while (canonicalCursor && pageIndex < loadedPages)\n\n if (useCanonicalInteractions) {\n setActivities(sortTimelineActivities(canonicalItems))\n setHasMore(canonicalHasMore)\n return\n }\n\n // In legacy mode, also fetch legacy activities and merge with canonical\n const legacyItems: InteractionSummary[] = []\n let legacyTotalPages = 1\n for (let legacyPage = 1; legacyPage <= loadedPages; legacyPage += 1) {\n const legacyParams = new URLSearchParams({\n entityId,\n page: String(legacyPage),\n pageSize: '50',\n sortField: 'occurredAt',\n sortDir: 'desc',\n })\n if (dealId) legacyParams.set('dealId', dealId)\n const legacyPayload = await readApiResultOrThrow<{ items?: ActivitySummary[]; totalPages?: number }>(\n `/api/customers/activities?${legacyParams.toString()}`,\n ).catch(() => ({ items: [] as ActivitySummary[], totalPages: 1 }))\n legacyItems.push(...(Array.isArray(legacyPayload?.items) ? legacyPayload.items.map(normalizeLegacyActivity) : []))\n legacyTotalPages = typeof legacyPayload?.totalPages === 'number' ? legacyPayload.totalPages : legacyTotalPages\n }\n const legacyFiltered = legacyItems.filter((entry) => {\n if (filterTypes.length > 0 && !filterTypes.includes(entry.interactionType)) return false\n const dateOnly = toDateOnly(entry.occurredAt ?? entry.createdAt)\n if (filterDateFrom && dateOnly < filterDateFrom) return false\n if (filterDateTo && dateOnly > filterDateTo) return false\n return true\n })\n\n // Merge and deduplicate by id, sort newest first\n const seen = new Set<string>()\n const merged: InteractionSummary[] = []\n for (const item of [...canonicalItems, ...legacyFiltered]) {\n if (!seen.has(item.id)) {\n seen.add(item.id)\n merged.push(item)\n }\n }\n setActivities(sortTimelineActivities(merged))\n setHasMore(canonicalHasMore || legacyTotalPages > loadedPages)\n } catch (error) {\n console.error('customers.activities.history failed', error)\n flash(t('customers.activities.loadFailed', 'Failed to load activities.'), 'error')\n setActivities([])\n setHasMore(false)\n } finally {\n setLoading(false)\n }\n }, [dealId, entityId, filterDateFrom, filterDateTo, filterTypes, loadedPages, useCanonicalInteractions, refreshKey, t])\n\n React.useEffect(() => {\n setLoadedPages(1)\n }, [dealId, entityId, filterDateFrom, filterDateTo, filterTypes, useCanonicalInteractions])\n\n const handleMarkDone = React.useCallback(async (activityId: string) => {\n try {\n const operation = () =>\n apiCallOrThrow('/api/customers/interactions/complete', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: activityId, occurredAt: new Date().toISOString() }),\n })\n if (runGuardedMutation) {\n await runGuardedMutation(operation, {\n id: activityId,\n status: 'done',\n operation: 'completeActivity',\n })\n } else {\n await operation()\n }\n flash(t('customers.activities.actions.markDoneSuccess', 'Activity marked done'), 'success')\n await loadActivities()\n } catch (err) {\n console.warn('[customers.activitiesSection] mark done failed', activityId, err)\n flash(t('customers.activities.actions.markDoneError', 'Could not mark activity as done'), 'error')\n }\n }, [loadActivities, runGuardedMutation, t])\n\n const resolvedUserIdsRef = React.useRef(new Set<string>())\n\n // Resolve missing author names from user IDs\n React.useEffect(() => {\n loadActivities()\n .then(() => { resolvedUserIdsRef.current = new Set() })\n .catch((err) => console.warn('[ActivitiesSection] loadActivities failed', err))\n }, [loadActivities])\n\n React.useEffect(() => {\n const unresolvedIds = new Set<string>()\n for (const a of activities) {\n if (a.authorUserId && !a.authorName && !resolvedUserIdsRef.current.has(a.authorUserId)) {\n unresolvedIds.add(a.authorUserId)\n }\n }\n if (unresolvedIds.size === 0) return\n\n for (const uid of unresolvedIds) resolvedUserIdsRef.current.add(uid)\n\n const controller = new AbortController()\n readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(\n `/api/auth/users?ids=${[...unresolvedIds].join(',')}`,\n { signal: controller.signal },\n )\n .then((data) => {\n const users = Array.isArray(data?.items) ? data.items : []\n const nameMap = new Map<string, string>()\n for (const user of users) {\n const userId = typeof user.id === 'string' ? user.id : null\n const name = typeof user.display_name === 'string' && user.display_name.trim()\n ? user.display_name.trim()\n : typeof user.email === 'string'\n ? user.email\n : null\n if (userId && name) nameMap.set(userId, name)\n }\n if (nameMap.size > 0) {\n setActivities((prev) =>\n prev.map((a) => {\n if (a.authorUserId && !a.authorName && nameMap.has(a.authorUserId)) {\n return { ...a, authorName: nameMap.get(a.authorUserId) ?? null }\n }\n return a\n }),\n )\n }\n })\n .catch((err) => console.warn('[ActivitiesSection] resolve author names failed', err))\n return () => controller.abort()\n }, [activities])\n\n const totalCount = activities.length\n const visibleCount = visibleActivities.length\n\n return (\n <div className=\"flex flex-col gap-3.5 rounded-[10px] border border-border bg-card pt-4 pb-[18px] px-[18px]\">\n <div className=\"flex items-center gap-2\">\n <Clock className=\"size-[15px] text-muted-foreground\" />\n <h3 className=\"text-[13px] font-semibold text-foreground\">\n {entityName\n ? t('customers.timeline.history.title', 'Interaction history with {{name}}', { name: entityName })\n : t('customers.timeline.history.titleGeneric', 'Interaction history')}\n </h3>\n </div>\n\n <label className=\"relative flex items-center\">\n <Search className=\"pointer-events-none absolute left-2.5 size-5 text-muted-foreground\" aria-hidden />\n <input\n ref={searchInputRef}\n type=\"search\"\n value={searchTerm}\n onChange={(event) => setSearchTerm(event.target.value)}\n placeholder={t('customers.timeline.history.searchPlaceholder', 'Search...')}\n aria-label={t('customers.timeline.history.searchAriaLabel', 'Search interaction history')}\n className=\"h-9 w-full rounded-[10px] border border-border bg-card pl-9 pr-14 text-sm text-foreground shadow-xs placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring [&::-webkit-search-cancel-button]:hidden [&::-webkit-search-decoration]:hidden\"\n />\n <Kbd className=\"pointer-events-none absolute right-2 hidden text-[11px] uppercase tracking-[0.48px] sm:inline-flex\">\n \u23181\n </Kbd>\n </label>\n\n <ActivityTimelineFilters\n entityId={entityId}\n activeTypes={filterTypes}\n dateFrom={filterDateFrom}\n dateTo={filterDateTo}\n onTypesChange={setFilterTypes}\n onDateFromChange={setFilterDateFrom}\n onDateToChange={setFilterDateTo}\n onReset={() => {\n setFilterTypes([])\n setFilterDateFrom('')\n setFilterDateTo('')\n }}\n />\n\n {loading && totalCount === 0 ? (\n <div className=\"rounded-lg border border-dashed border-border/70 px-4 py-8 text-sm text-muted-foreground\">\n {t('customers.people.detail.activities.loading', 'Loading activities\u2026')}\n </div>\n ) : (\n <>\n <ActivityTimeline\n activities={visibleActivities}\n onEdit={onEditActivity}\n onMarkDone={handleMarkDone}\n />\n {totalCount > 0 ? (\n <div className=\"flex items-center justify-between gap-3 border-t border-border/60 pt-3\">\n <span className=\"text-xs text-muted-foreground\">\n {searchTerm.trim()\n ? t('customers.activities.seeMatching', 'Showing {visible} of {total} activities', {\n visible: visibleCount,\n total: totalCount,\n })\n : t('customers.activities.seeAll', 'See all {count} activities', { count: totalCount })}\n </span>\n {hasMore ? (\n <Button type=\"button\" variant=\"link\" size=\"sm\" onClick={() => setLoadedPages((value) => value + 1)}>\n {t('customers.activities.loadMore', 'Load more')}\n </Button>\n ) : null}\n </div>\n ) : null}\n </>\n )}\n </div>\n )\n}\n\nexport default ActivitiesSection\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }