@open-mercato/core 0.5.1-develop.2996.ce62fd491c → 0.5.1-develop.3032.01699048cb
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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/customers/api/companies/[id]/route.js +30 -20
- package/dist/modules/customers/api/companies/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/companies/route.js +12 -7
- package/dist/modules/customers/api/companies/route.js.map +2 -2
- package/dist/modules/customers/api/people/[id]/companies/enriched/route.js +12 -7
- package/dist/modules/customers/api/people/[id]/companies/enriched/route.js.map +2 -2
- package/dist/modules/customers/api/people/route.js +12 -7
- package/dist/modules/customers/api/people/route.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +21 -0
- package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +27 -30
- package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivitiesAddNewMenu.js +56 -0
- package/dist/modules/customers/components/detail/ActivitiesAddNewMenu.js.map +7 -0
- package/dist/modules/customers/components/detail/ActivitiesCard.js +175 -0
- package/dist/modules/customers/components/detail/ActivitiesCard.js.map +7 -0
- package/dist/modules/customers/components/detail/ActivitiesDayStrip.js +324 -0
- package/dist/modules/customers/components/detail/ActivitiesDayStrip.js.map +7 -0
- package/dist/modules/customers/components/detail/ActivitiesSection.js +62 -13
- package/dist/modules/customers/components/detail/ActivitiesSection.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivityLogTab.js +14 -23
- package/dist/modules/customers/components/detail/ActivityLogTab.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivityTimeline.js +13 -13
- package/dist/modules/customers/components/detail/ActivityTimeline.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivityTimelineFilters.js +35 -22
- package/dist/modules/customers/components/detail/ActivityTimelineFilters.js.map +2 -2
- package/dist/modules/customers/components/detail/AiActionChips.js +15 -22
- package/dist/modules/customers/components/detail/AiActionChips.js.map +2 -2
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +196 -28
- package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/DateTimeFields.js +2 -2
- package/dist/modules/customers/components/detail/schedule/DateTimeFields.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/FooterFields.js +14 -2
- package/dist/modules/customers/components/detail/schedule/FooterFields.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/LinkedEntitiesField.js +9 -2
- package/dist/modules/customers/components/detail/schedule/LinkedEntitiesField.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +9 -2
- package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/fieldConfig.js +25 -4
- package/dist/modules/customers/components/detail/schedule/fieldConfig.js.map +2 -2
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +20 -3
- package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
- package/package.json +3 -3
- package/src/modules/customers/api/companies/[id]/route.ts +30 -20
- package/src/modules/customers/api/companies/route.ts +12 -7
- package/src/modules/customers/api/people/[id]/companies/enriched/route.ts +12 -7
- package/src/modules/customers/api/people/route.ts +12 -7
- package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +22 -0
- package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +28 -21
- package/src/modules/customers/components/detail/ActivitiesAddNewMenu.tsx +67 -0
- package/src/modules/customers/components/detail/ActivitiesCard.tsx +231 -0
- package/src/modules/customers/components/detail/ActivitiesDayStrip.tsx +390 -0
- package/src/modules/customers/components/detail/ActivitiesSection.tsx +91 -40
- package/src/modules/customers/components/detail/ActivityLogTab.tsx +25 -23
- package/src/modules/customers/components/detail/ActivityTimeline.tsx +15 -19
- package/src/modules/customers/components/detail/ActivityTimelineFilters.tsx +36 -29
- package/src/modules/customers/components/detail/AiActionChips.tsx +17 -23
- package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +233 -41
- package/src/modules/customers/components/detail/schedule/DateTimeFields.tsx +6 -2
- package/src/modules/customers/components/detail/schedule/FooterFields.tsx +22 -2
- package/src/modules/customers/components/detail/schedule/LinkedEntitiesField.tsx +10 -2
- package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +10 -2
- package/src/modules/customers/components/detail/schedule/fieldConfig.ts +26 -6
- package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +32 -3
- package/src/modules/customers/i18n/de.json +69 -2
- package/src/modules/customers/i18n/en.json +69 -2
- package/src/modules/customers/i18n/es.json +69 -2
- package/src/modules/customers/i18n/pl.json +68 -1
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Calendar, CalendarClock, Clock, Mail, Phone, StickyNote, Users } from "lucide-react";
|
|
5
|
+
import { cn } from "@open-mercato/shared/lib/utils";
|
|
6
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
7
|
+
import { ActivitiesDayStrip } from "./ActivitiesDayStrip.js";
|
|
8
|
+
import { ActivitiesAddNewMenu } from "./ActivitiesAddNewMenu.js";
|
|
9
|
+
const TYPE_ICONS = {
|
|
10
|
+
call: Phone,
|
|
11
|
+
email: Mail,
|
|
12
|
+
meeting: Users,
|
|
13
|
+
note: StickyNote
|
|
14
|
+
};
|
|
15
|
+
function startOfDay(date) {
|
|
16
|
+
const next = new Date(date);
|
|
17
|
+
next.setHours(0, 0, 0, 0);
|
|
18
|
+
return next;
|
|
19
|
+
}
|
|
20
|
+
function isSameDay(a, b) {
|
|
21
|
+
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
22
|
+
}
|
|
23
|
+
function isOverdue(activity, now) {
|
|
24
|
+
const scheduled = activity.scheduledAt ?? activity.occurredAt;
|
|
25
|
+
if (!scheduled) return false;
|
|
26
|
+
const date = new Date(scheduled);
|
|
27
|
+
if (Number.isNaN(date.getTime())) return false;
|
|
28
|
+
return date.getTime() < now.getTime() && activity.status !== "done";
|
|
29
|
+
}
|
|
30
|
+
function formatTime(date) {
|
|
31
|
+
return date.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" });
|
|
32
|
+
}
|
|
33
|
+
function formatRelativeDay(date, t) {
|
|
34
|
+
const now = /* @__PURE__ */ new Date();
|
|
35
|
+
const today = startOfDay(now);
|
|
36
|
+
const target = startOfDay(date);
|
|
37
|
+
const diff = Math.round((target.getTime() - today.getTime()) / (1e3 * 60 * 60 * 24));
|
|
38
|
+
if (diff === 0) return t("customers.timeline.date.today", "today");
|
|
39
|
+
if (diff === 1) return t("customers.timeline.date.tomorrow", "tomorrow");
|
|
40
|
+
if (diff === -1) return t("customers.timeline.date.yesterday", "yesterday");
|
|
41
|
+
return target.toLocaleDateString(void 0, { day: "numeric", month: "short" });
|
|
42
|
+
}
|
|
43
|
+
function formatDuration(minutes, t) {
|
|
44
|
+
if (minutes >= 60) {
|
|
45
|
+
const hours = Math.round(minutes / 60 * 10) / 10;
|
|
46
|
+
return t("customers.activities.calendar.hoursShort", "{hours}h", { hours });
|
|
47
|
+
}
|
|
48
|
+
return t("customers.activities.calendar.minutesShort", "{minutes}m", { minutes });
|
|
49
|
+
}
|
|
50
|
+
function ActivitiesCard({
|
|
51
|
+
entityId,
|
|
52
|
+
plannedActivities,
|
|
53
|
+
refreshKey = 0,
|
|
54
|
+
onAddNew,
|
|
55
|
+
onEditActivity,
|
|
56
|
+
entityCompanyName
|
|
57
|
+
}) {
|
|
58
|
+
const t = useT();
|
|
59
|
+
const [selectedDate, setSelectedDate] = React.useState(() => startOfDay(/* @__PURE__ */ new Date()));
|
|
60
|
+
const eventsForSelectedDay = React.useMemo(() => {
|
|
61
|
+
const items = plannedActivities.filter((activity) => {
|
|
62
|
+
const scheduled = activity.scheduledAt ?? activity.occurredAt;
|
|
63
|
+
if (!scheduled) return false;
|
|
64
|
+
const date = new Date(scheduled);
|
|
65
|
+
if (Number.isNaN(date.getTime())) return false;
|
|
66
|
+
return isSameDay(date, selectedDate);
|
|
67
|
+
});
|
|
68
|
+
return items.sort((left, right) => {
|
|
69
|
+
const leftTime = new Date(left.scheduledAt ?? left.occurredAt ?? left.createdAt).getTime();
|
|
70
|
+
const rightTime = new Date(right.scheduledAt ?? right.occurredAt ?? right.createdAt).getTime();
|
|
71
|
+
return leftTime - rightTime;
|
|
72
|
+
});
|
|
73
|
+
}, [plannedActivities, selectedDate]);
|
|
74
|
+
const overdueCount = React.useMemo(() => {
|
|
75
|
+
const now = /* @__PURE__ */ new Date();
|
|
76
|
+
return plannedActivities.filter((activity) => isOverdue(activity, now)).length;
|
|
77
|
+
}, [plannedActivities]);
|
|
78
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 rounded-lg border border-border bg-card pt-4 pb-4 px-4", children: [
|
|
79
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
80
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
81
|
+
/* @__PURE__ */ jsx(Calendar, { className: "size-4 text-foreground" }),
|
|
82
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold leading-none text-foreground", children: t("customers.activities.card.title", "Activities") }),
|
|
83
|
+
overdueCount > 0 ? /* @__PURE__ */ jsxs("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", children: [
|
|
84
|
+
/* @__PURE__ */ jsx(CalendarClock, { className: "size-3" }),
|
|
85
|
+
t("customers.activities.card.overdue", "{count} overdue", { count: overdueCount })
|
|
86
|
+
] }) : null
|
|
87
|
+
] }),
|
|
88
|
+
/* @__PURE__ */ jsx(ActivitiesAddNewMenu, { onSelect: onAddNew })
|
|
89
|
+
] }),
|
|
90
|
+
/* @__PURE__ */ jsx(
|
|
91
|
+
ActivitiesDayStrip,
|
|
92
|
+
{
|
|
93
|
+
entityId,
|
|
94
|
+
selectedDate,
|
|
95
|
+
onSelectDate: setSelectedDate,
|
|
96
|
+
refreshKey
|
|
97
|
+
}
|
|
98
|
+
),
|
|
99
|
+
eventsForSelectedDay.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
100
|
+
/* @__PURE__ */ jsx("div", { className: "h-px w-full bg-border" }),
|
|
101
|
+
/* @__PURE__ */ jsx("ul", { className: "flex flex-col", children: eventsForSelectedDay.map((activity) => /* @__PURE__ */ jsx(
|
|
102
|
+
PlannedEventRow,
|
|
103
|
+
{
|
|
104
|
+
activity,
|
|
105
|
+
onClick: onEditActivity,
|
|
106
|
+
entityCompanyName: entityCompanyName ?? null,
|
|
107
|
+
t
|
|
108
|
+
},
|
|
109
|
+
activity.id
|
|
110
|
+
)) })
|
|
111
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
112
|
+
/* @__PURE__ */ jsx("div", { className: "h-px w-full bg-border" }),
|
|
113
|
+
/* @__PURE__ */ jsx("p", { className: "px-1 py-2 text-xs text-muted-foreground", children: t("customers.activities.card.empty", "Nothing scheduled for this day.") })
|
|
114
|
+
] })
|
|
115
|
+
] });
|
|
116
|
+
}
|
|
117
|
+
function PlannedEventRow({ activity, onClick, entityCompanyName, t }) {
|
|
118
|
+
const dateStr = activity.scheduledAt ?? activity.occurredAt ?? activity.createdAt;
|
|
119
|
+
const date = new Date(dateStr);
|
|
120
|
+
const validDate = !Number.isNaN(date.getTime());
|
|
121
|
+
const Icon = TYPE_ICONS[activity.interactionType] ?? Users;
|
|
122
|
+
const duration = typeof activity.duration === "number" && activity.duration > 0 ? activity.duration : null;
|
|
123
|
+
const overdue = validDate && date.getTime() < Date.now() && activity.status !== "done";
|
|
124
|
+
const typeLabel = labelForType(activity.interactionType, t);
|
|
125
|
+
const subtitleSuffix = activity.dealTitle ?? entityCompanyName ?? null;
|
|
126
|
+
const subtitle = subtitleSuffix ? `${typeLabel} \xB7 ${subtitleSuffix}` : typeLabel;
|
|
127
|
+
const interactive = !!onClick;
|
|
128
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
|
|
129
|
+
"button",
|
|
130
|
+
{
|
|
131
|
+
type: "button",
|
|
132
|
+
onClick: interactive ? () => onClick?.(activity) : void 0,
|
|
133
|
+
disabled: !interactive,
|
|
134
|
+
className: cn(
|
|
135
|
+
"flex w-full items-start gap-[9px] pt-[8px] text-left transition-colors",
|
|
136
|
+
interactive ? "cursor-pointer rounded-md hover:bg-accent/30 px-1" : "px-1"
|
|
137
|
+
),
|
|
138
|
+
children: [
|
|
139
|
+
/* @__PURE__ */ jsxs("div", { className: "flex h-[44px] w-[43px] shrink-0 flex-col gap-[2px] pt-[2px]", children: [
|
|
140
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-semibold leading-none text-foreground", children: validDate ? formatTime(date) : "" }),
|
|
141
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] leading-none font-normal text-muted-foreground", children: validDate ? formatRelativeDay(date, t) : "" })
|
|
142
|
+
] }),
|
|
143
|
+
/* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center justify-center rounded-full bg-muted border-4 border-background size-7", children: /* @__PURE__ */ jsx(Icon, { className: "size-4 text-muted-foreground" }) }),
|
|
144
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex flex-1 flex-col gap-[4px]", children: [
|
|
145
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm leading-5 tracking-[-0.084px] text-foreground", children: activity.title ?? activity.body ?? labelForType(activity.interactionType, t) }),
|
|
146
|
+
duration ? /* @__PURE__ */ jsxs("span", { className: cn(
|
|
147
|
+
"inline-flex w-fit items-center gap-[2px] rounded-full pl-[4px] pr-[8px] py-[2px] text-xs font-medium leading-[16px]",
|
|
148
|
+
overdue ? "bg-status-error-bg text-status-error-text" : "bg-status-warning-bg text-status-warning-text"
|
|
149
|
+
), children: [
|
|
150
|
+
/* @__PURE__ */ jsx(Clock, { className: "size-4" }),
|
|
151
|
+
formatDuration(duration, t)
|
|
152
|
+
] }) : null,
|
|
153
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] font-normal text-muted-foreground", children: subtitle })
|
|
154
|
+
] })
|
|
155
|
+
]
|
|
156
|
+
}
|
|
157
|
+
) });
|
|
158
|
+
}
|
|
159
|
+
function labelForType(type, t) {
|
|
160
|
+
const map = {
|
|
161
|
+
meeting: ["customers.timeline.filter.meeting", "Meeting"],
|
|
162
|
+
call: ["customers.timeline.filter.call", "Call"],
|
|
163
|
+
email: ["customers.timeline.filter.email", "Email"],
|
|
164
|
+
note: ["customers.timeline.filter.note", "Note"],
|
|
165
|
+
task: ["customers.timeline.filter.task", "Task"]
|
|
166
|
+
};
|
|
167
|
+
const entry = map[type];
|
|
168
|
+
return entry ? t(entry[0], entry[1]) : type;
|
|
169
|
+
}
|
|
170
|
+
var ActivitiesCard_default = ActivitiesCard;
|
|
171
|
+
export {
|
|
172
|
+
ActivitiesCard,
|
|
173
|
+
ActivitiesCard_default as default
|
|
174
|
+
};
|
|
175
|
+
//# sourceMappingURL=ActivitiesCard.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
5
|
+
import { cn } from "@open-mercato/shared/lib/utils";
|
|
6
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
7
|
+
import { readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
|
|
8
|
+
const VISIBLE_DAYS = 5;
|
|
9
|
+
const BUSYNESS_SLOTS = 10;
|
|
10
|
+
const SLOT_START_HOUR = 7;
|
|
11
|
+
const SLOT_END_HOUR = 22;
|
|
12
|
+
const DAY_LABEL_KEYS = [
|
|
13
|
+
[0, "customers.calendar.day.sun", "SUN"],
|
|
14
|
+
[1, "customers.calendar.day.mon", "MON"],
|
|
15
|
+
[2, "customers.calendar.day.tue", "TUE"],
|
|
16
|
+
[3, "customers.calendar.day.wed", "WED"],
|
|
17
|
+
[4, "customers.calendar.day.thu", "THU"],
|
|
18
|
+
[5, "customers.calendar.day.fri", "FRI"],
|
|
19
|
+
[6, "customers.calendar.day.sat", "SAT"]
|
|
20
|
+
];
|
|
21
|
+
const MONTH_KEYS = [
|
|
22
|
+
[0, "customers.calendar.month.january", "January"],
|
|
23
|
+
[1, "customers.calendar.month.february", "February"],
|
|
24
|
+
[2, "customers.calendar.month.march", "March"],
|
|
25
|
+
[3, "customers.calendar.month.april", "April"],
|
|
26
|
+
[4, "customers.calendar.month.may", "May"],
|
|
27
|
+
[5, "customers.calendar.month.june", "June"],
|
|
28
|
+
[6, "customers.calendar.month.july", "July"],
|
|
29
|
+
[7, "customers.calendar.month.august", "August"],
|
|
30
|
+
[8, "customers.calendar.month.september", "September"],
|
|
31
|
+
[9, "customers.calendar.month.october", "October"],
|
|
32
|
+
[10, "customers.calendar.month.november", "November"],
|
|
33
|
+
[11, "customers.calendar.month.december", "December"]
|
|
34
|
+
];
|
|
35
|
+
function startOfDay(date) {
|
|
36
|
+
const next = new Date(date);
|
|
37
|
+
next.setHours(0, 0, 0, 0);
|
|
38
|
+
return next;
|
|
39
|
+
}
|
|
40
|
+
function endOfDay(date) {
|
|
41
|
+
const next = new Date(date);
|
|
42
|
+
next.setHours(23, 59, 59, 999);
|
|
43
|
+
return next;
|
|
44
|
+
}
|
|
45
|
+
function isSameDay(a, b) {
|
|
46
|
+
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
47
|
+
}
|
|
48
|
+
function isWeekend(date) {
|
|
49
|
+
const day = date.getDay();
|
|
50
|
+
return day === 0 || day === 6;
|
|
51
|
+
}
|
|
52
|
+
function addDays(date, delta) {
|
|
53
|
+
const next = new Date(date);
|
|
54
|
+
next.setDate(date.getDate() + delta);
|
|
55
|
+
return next;
|
|
56
|
+
}
|
|
57
|
+
function buildVisibleDays(anchor) {
|
|
58
|
+
const start = startOfDay(anchor);
|
|
59
|
+
return Array.from({ length: VISIBLE_DAYS }, (_, index) => addDays(start, index));
|
|
60
|
+
}
|
|
61
|
+
function anchorCenteredOn(focalDate) {
|
|
62
|
+
return startOfDay(addDays(focalDate, -Math.floor(VISIBLE_DAYS / 2)));
|
|
63
|
+
}
|
|
64
|
+
function emptyBusyness() {
|
|
65
|
+
return {
|
|
66
|
+
totalMinutes: 0,
|
|
67
|
+
eventCount: 0,
|
|
68
|
+
slots: Array(BUSYNESS_SLOTS).fill("empty")
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function computeDayBusyness(events, day) {
|
|
72
|
+
if (events.length === 0) return emptyBusyness();
|
|
73
|
+
const dayStart = startOfDay(day).getTime();
|
|
74
|
+
const slotMs = (SLOT_END_HOUR - SLOT_START_HOUR) * 60 * 60 * 1e3 / BUSYNESS_SLOTS;
|
|
75
|
+
const slotMinutes = slotMs / 6e4;
|
|
76
|
+
const slotCounts = Array(BUSYNESS_SLOTS).fill(0);
|
|
77
|
+
const slotMinutesUsed = Array(BUSYNESS_SLOTS).fill(0);
|
|
78
|
+
let totalMinutes = 0;
|
|
79
|
+
let eventCount = 0;
|
|
80
|
+
for (const event of events) {
|
|
81
|
+
const startIso = event.scheduledAt ?? event.occurredAt ?? event.createdAt;
|
|
82
|
+
if (!startIso) continue;
|
|
83
|
+
const start = new Date(startIso);
|
|
84
|
+
if (Number.isNaN(start.getTime())) continue;
|
|
85
|
+
if (!isSameDay(start, day)) continue;
|
|
86
|
+
eventCount += 1;
|
|
87
|
+
const durationMinutes = typeof event.duration === "number" && event.duration > 0 ? event.duration : 30;
|
|
88
|
+
totalMinutes += durationMinutes;
|
|
89
|
+
const eventStartMs = start.getTime();
|
|
90
|
+
const eventEndMs = eventStartMs + durationMinutes * 6e4;
|
|
91
|
+
const slotsStartMs = dayStart + SLOT_START_HOUR * 60 * 60 * 1e3;
|
|
92
|
+
for (let slot = 0; slot < BUSYNESS_SLOTS; slot += 1) {
|
|
93
|
+
const slotStart = slotsStartMs + slot * slotMs;
|
|
94
|
+
const slotEnd = slotStart + slotMs;
|
|
95
|
+
const overlapStart = Math.max(slotStart, eventStartMs);
|
|
96
|
+
const overlapEnd = Math.min(slotEnd, eventEndMs);
|
|
97
|
+
const overlapMinutes = Math.max(0, (overlapEnd - overlapStart) / 6e4);
|
|
98
|
+
if (overlapMinutes <= 0) continue;
|
|
99
|
+
slotCounts[slot] += 1;
|
|
100
|
+
slotMinutesUsed[slot] += overlapMinutes;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const slots = slotCounts.map((count, index) => {
|
|
104
|
+
if (count === 0) return "empty";
|
|
105
|
+
if (count > 1) return "conflict";
|
|
106
|
+
const used = slotMinutesUsed[index];
|
|
107
|
+
if (used >= slotMinutes * 0.5) return "full";
|
|
108
|
+
return "partial";
|
|
109
|
+
});
|
|
110
|
+
return { totalMinutes, eventCount, slots };
|
|
111
|
+
}
|
|
112
|
+
function formatBusyLabel(busy, t) {
|
|
113
|
+
if (busy.eventCount === 0) return "";
|
|
114
|
+
const durationLabel = busy.totalMinutes < 60 ? t("customers.activities.calendar.minutesShort", "{minutes}m", { minutes: Math.max(Math.round(busy.totalMinutes), 1) }) : t("customers.activities.calendar.hoursShort", "{hours}h", { hours: Math.floor(busy.totalMinutes / 60) });
|
|
115
|
+
return t("customers.activities.calendar.eventsSummary", "{count} {countLabel} \xB7 {duration}", {
|
|
116
|
+
count: busy.eventCount,
|
|
117
|
+
countLabel: busy.eventCount === 1 ? t("customers.activities.calendar.eventSingular", "event") : t("customers.activities.calendar.eventPlural", "events"),
|
|
118
|
+
duration: durationLabel
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function formatMonthLabel(date, t) {
|
|
122
|
+
const monthEntry = MONTH_KEYS.find(([index]) => index === date.getMonth());
|
|
123
|
+
const monthName = monthEntry ? t(monthEntry[1], monthEntry[2]) : "";
|
|
124
|
+
return t("customers.activities.calendar.monthYear", "{month} {year}", { month: monthName, year: date.getFullYear() });
|
|
125
|
+
}
|
|
126
|
+
function formatDayLabel(date, t) {
|
|
127
|
+
const entry = DAY_LABEL_KEYS.find(([index]) => index === date.getDay());
|
|
128
|
+
return entry ? t(entry[1], entry[2]) : "";
|
|
129
|
+
}
|
|
130
|
+
function ActivitiesDayStrip({ entityId, selectedDate, onSelectDate, refreshKey = 0 }) {
|
|
131
|
+
const t = useT();
|
|
132
|
+
const [anchor, setAnchor] = React.useState(() => anchorCenteredOn(selectedDate));
|
|
133
|
+
const [events, setEvents] = React.useState([]);
|
|
134
|
+
React.useEffect(() => {
|
|
135
|
+
setAnchor((current) => {
|
|
136
|
+
const days = buildVisibleDays(current);
|
|
137
|
+
const visible = days.some((day) => isSameDay(day, selectedDate));
|
|
138
|
+
if (visible) return current;
|
|
139
|
+
return anchorCenteredOn(selectedDate);
|
|
140
|
+
});
|
|
141
|
+
}, [selectedDate]);
|
|
142
|
+
const visibleDays = React.useMemo(() => buildVisibleDays(anchor), [anchor]);
|
|
143
|
+
const headerLabel = React.useMemo(() => formatMonthLabel(visibleDays[0], t), [visibleDays, t]);
|
|
144
|
+
React.useEffect(() => {
|
|
145
|
+
if (!entityId || visibleDays.length === 0) return;
|
|
146
|
+
const controller = new AbortController();
|
|
147
|
+
const fromIso = startOfDay(visibleDays[0]).toISOString();
|
|
148
|
+
const toIso = endOfDay(visibleDays[visibleDays.length - 1]).toISOString();
|
|
149
|
+
const params = new URLSearchParams({
|
|
150
|
+
entityId,
|
|
151
|
+
from: fromIso,
|
|
152
|
+
to: toIso,
|
|
153
|
+
limit: "100",
|
|
154
|
+
sortField: "scheduledAt",
|
|
155
|
+
sortDir: "asc",
|
|
156
|
+
excludeInteractionType: "task"
|
|
157
|
+
});
|
|
158
|
+
void (async () => {
|
|
159
|
+
try {
|
|
160
|
+
const payload = await readApiResultOrThrow(
|
|
161
|
+
`/api/customers/interactions?${params.toString()}`,
|
|
162
|
+
{ signal: controller.signal }
|
|
163
|
+
);
|
|
164
|
+
setEvents(Array.isArray(payload?.items) ? payload.items : []);
|
|
165
|
+
} catch (err) {
|
|
166
|
+
if (err?.name !== "AbortError") {
|
|
167
|
+
console.warn("[ActivitiesDayStrip] failed to load interactions", err);
|
|
168
|
+
}
|
|
169
|
+
setEvents([]);
|
|
170
|
+
}
|
|
171
|
+
})();
|
|
172
|
+
return () => controller.abort();
|
|
173
|
+
}, [entityId, visibleDays, refreshKey]);
|
|
174
|
+
const todayDate = React.useMemo(() => startOfDay(/* @__PURE__ */ new Date()), []);
|
|
175
|
+
const handlePrev = React.useCallback(() => {
|
|
176
|
+
setAnchor((current) => addDays(current, -VISIBLE_DAYS));
|
|
177
|
+
}, []);
|
|
178
|
+
const handleNext = React.useCallback(() => {
|
|
179
|
+
setAnchor((current) => addDays(current, VISIBLE_DAYS));
|
|
180
|
+
}, []);
|
|
181
|
+
const handleHeaderPrev = React.useCallback(() => {
|
|
182
|
+
setAnchor((current) => {
|
|
183
|
+
const next = new Date(current);
|
|
184
|
+
next.setMonth(current.getMonth() - 1);
|
|
185
|
+
return startOfDay(next);
|
|
186
|
+
});
|
|
187
|
+
}, []);
|
|
188
|
+
const handleHeaderNext = React.useCallback(() => {
|
|
189
|
+
setAnchor((current) => {
|
|
190
|
+
const next = new Date(current);
|
|
191
|
+
next.setMonth(current.getMonth() + 1);
|
|
192
|
+
return startOfDay(next);
|
|
193
|
+
});
|
|
194
|
+
}, []);
|
|
195
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2.5 rounded-md px-3.5 py-3 w-full", children: [
|
|
196
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-1.5 rounded-md bg-muted px-1.5 py-1.5", children: [
|
|
197
|
+
/* @__PURE__ */ jsx(
|
|
198
|
+
"button",
|
|
199
|
+
{
|
|
200
|
+
type: "button",
|
|
201
|
+
onClick: handleHeaderPrev,
|
|
202
|
+
"aria-label": t("customers.activities.calendar.prevMonth", "Previous month"),
|
|
203
|
+
className: "flex size-6 items-center justify-center rounded-md border border-border bg-card shadow-xs hover:bg-accent/40",
|
|
204
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4 text-foreground" })
|
|
205
|
+
}
|
|
206
|
+
),
|
|
207
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 text-center text-sm font-medium leading-5 text-foreground", children: headerLabel }),
|
|
208
|
+
/* @__PURE__ */ jsx(
|
|
209
|
+
"button",
|
|
210
|
+
{
|
|
211
|
+
type: "button",
|
|
212
|
+
onClick: handleHeaderNext,
|
|
213
|
+
"aria-label": t("customers.activities.calendar.nextMonth", "Next month"),
|
|
214
|
+
className: "flex size-6 items-center justify-center rounded-md border border-border bg-card shadow-xs hover:bg-accent/40",
|
|
215
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4 text-foreground" })
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
] }),
|
|
219
|
+
/* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-2", children: [
|
|
220
|
+
/* @__PURE__ */ jsx(
|
|
221
|
+
"button",
|
|
222
|
+
{
|
|
223
|
+
type: "button",
|
|
224
|
+
onClick: handlePrev,
|
|
225
|
+
"aria-label": t("customers.activities.calendar.prevWindow", "Previous days"),
|
|
226
|
+
className: "flex size-6 shrink-0 items-center justify-center rounded-md border border-border bg-card shadow-xs hover:bg-accent/40",
|
|
227
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { className: "size-4 text-foreground" })
|
|
228
|
+
}
|
|
229
|
+
),
|
|
230
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-1 items-stretch justify-center gap-1", children: visibleDays.map((day) => {
|
|
231
|
+
const busy = computeDayBusyness(events, day);
|
|
232
|
+
const isSelected = isSameDay(day, selectedDate);
|
|
233
|
+
const isToday = isSameDay(day, todayDate);
|
|
234
|
+
const weekend = isWeekend(day);
|
|
235
|
+
const busyLabel = busy.eventCount > 0 ? formatBusyLabel(busy, t) : weekend ? t("customers.activities.calendar.weekend", "Weekend") : "";
|
|
236
|
+
return /* @__PURE__ */ jsx(
|
|
237
|
+
DayCard,
|
|
238
|
+
{
|
|
239
|
+
day,
|
|
240
|
+
isActive: isSelected,
|
|
241
|
+
isToday,
|
|
242
|
+
busyness: busy,
|
|
243
|
+
label: busyLabel,
|
|
244
|
+
dayName: formatDayLabel(day, t),
|
|
245
|
+
onSelect: () => onSelectDate(day)
|
|
246
|
+
},
|
|
247
|
+
day.toISOString()
|
|
248
|
+
);
|
|
249
|
+
}) }),
|
|
250
|
+
/* @__PURE__ */ jsx(
|
|
251
|
+
"button",
|
|
252
|
+
{
|
|
253
|
+
type: "button",
|
|
254
|
+
onClick: handleNext,
|
|
255
|
+
"aria-label": t("customers.activities.calendar.nextWindow", "Next days"),
|
|
256
|
+
className: "flex size-6 shrink-0 items-center justify-center rounded-md border border-border bg-card shadow-xs hover:bg-accent/40",
|
|
257
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { className: "size-4 text-foreground" })
|
|
258
|
+
}
|
|
259
|
+
)
|
|
260
|
+
] })
|
|
261
|
+
] });
|
|
262
|
+
}
|
|
263
|
+
function DayCard({ day, isActive, isToday, busyness, label, dayName, onSelect }) {
|
|
264
|
+
const dayNumber = String(day.getDate()).padStart(2, "0");
|
|
265
|
+
return /* @__PURE__ */ jsxs(
|
|
266
|
+
"button",
|
|
267
|
+
{
|
|
268
|
+
type: "button",
|
|
269
|
+
onClick: onSelect,
|
|
270
|
+
"aria-pressed": isActive,
|
|
271
|
+
"aria-label": `${dayName} ${dayNumber}`,
|
|
272
|
+
className: cn(
|
|
273
|
+
"flex h-[104px] w-[101px] flex-col items-center gap-[6px] overflow-hidden rounded-[10px] border p-[12px] transition-colors",
|
|
274
|
+
isActive ? "border-transparent bg-foreground" : "border-border bg-card hover:border-foreground/40"
|
|
275
|
+
),
|
|
276
|
+
children: [
|
|
277
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium leading-none tracking-[0.44px] text-muted-foreground", children: dayName }),
|
|
278
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-[5px]", children: [
|
|
279
|
+
/* @__PURE__ */ jsx(
|
|
280
|
+
"span",
|
|
281
|
+
{
|
|
282
|
+
className: cn(
|
|
283
|
+
"text-2xl font-semibold leading-7",
|
|
284
|
+
isActive ? "text-background" : "text-foreground"
|
|
285
|
+
),
|
|
286
|
+
children: dayNumber
|
|
287
|
+
}
|
|
288
|
+
),
|
|
289
|
+
isToday ? /* @__PURE__ */ jsx(
|
|
290
|
+
"span",
|
|
291
|
+
{
|
|
292
|
+
className: "inline-block size-1.5 rounded-full bg-status-info-icon",
|
|
293
|
+
"aria-hidden": true
|
|
294
|
+
}
|
|
295
|
+
) : null
|
|
296
|
+
] }),
|
|
297
|
+
/* @__PURE__ */ jsx("div", { className: "flex h-4 w-[82px] items-end gap-[1.5px]", children: busyness.slots.map((state, index) => /* @__PURE__ */ jsx(BusySlot, { state, active: isActive }, index)) }),
|
|
298
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] leading-[14px] font-normal whitespace-nowrap text-muted-foreground", children: label })
|
|
299
|
+
]
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
function BusySlot({ state, active }) {
|
|
304
|
+
const heightClass = state === "empty" ? "h-0.5" : state === "partial" ? "h-2" : "h-3.5";
|
|
305
|
+
let bgClass;
|
|
306
|
+
if (state === "conflict") {
|
|
307
|
+
bgClass = "bg-status-error-icon";
|
|
308
|
+
} else if (active) {
|
|
309
|
+
if (state === "empty") bgClass = "bg-background/30";
|
|
310
|
+
else if (state === "partial") bgClass = "bg-background/60";
|
|
311
|
+
else bgClass = "bg-background";
|
|
312
|
+
} else {
|
|
313
|
+
if (state === "empty") bgClass = "bg-border";
|
|
314
|
+
else if (state === "partial") bgClass = "bg-muted-foreground";
|
|
315
|
+
else bgClass = "bg-foreground";
|
|
316
|
+
}
|
|
317
|
+
return /* @__PURE__ */ jsx("div", { className: cn("w-[7px] shrink-0 rounded-[1.5px]", heightClass, bgClass), "aria-hidden": true });
|
|
318
|
+
}
|
|
319
|
+
var ActivitiesDayStrip_default = ActivitiesDayStrip;
|
|
320
|
+
export {
|
|
321
|
+
ActivitiesDayStrip,
|
|
322
|
+
ActivitiesDayStrip_default as default
|
|
323
|
+
};
|
|
324
|
+
//# sourceMappingURL=ActivitiesDayStrip.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 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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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|