@open-mercato/core 0.5.1-develop.2996.ce62fd491c → 0.5.1-develop.3036.f02c281f23
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/auth/api/sidebar/preferences/route.js +2 -2
- package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
- package/dist/modules/auth/api/sidebar/variants/[id]/route.js +2 -2
- package/dist/modules/auth/api/sidebar/variants/[id]/route.js.map +2 -2
- package/dist/modules/auth/api/sidebar/variants/route.js +1 -1
- package/dist/modules/auth/api/sidebar/variants/route.js.map +2 -2
- package/dist/modules/auth/backend/sidebar-customization/page.meta.js +1 -0
- package/dist/modules/auth/backend/sidebar-customization/page.meta.js.map +2 -2
- 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/auth/api/sidebar/preferences/route.ts +2 -2
- package/src/modules/auth/api/sidebar/variants/[id]/route.ts +2 -2
- package/src/modules/auth/api/sidebar/variants/route.ts +1 -1
- package/src/modules/auth/backend/sidebar-customization/page.meta.ts +1 -8
- 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
|
@@ -3,7 +3,19 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { Bell, Eye, ChevronDown } from "lucide-react";
|
|
4
4
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
5
5
|
import { isVisible, getFieldLabel } from "./fieldConfig.js";
|
|
6
|
-
const REMINDER_OPTIONS = [0, 5, 10, 15, 30, 60];
|
|
6
|
+
const REMINDER_OPTIONS = [0, 5, 10, 15, 30, 60, 240, 1440];
|
|
7
|
+
function formatReminderLabel(minutes, t) {
|
|
8
|
+
if (minutes === 0) return t("customers.schedule.reminder.none", "None");
|
|
9
|
+
if (minutes >= 1440) {
|
|
10
|
+
const days = Math.round(minutes / 1440);
|
|
11
|
+
return days === 1 ? t("customers.schedule.reminder.dayBefore", "1 day before") : t("customers.schedule.reminder.daysBefore", "{days} days before", { days });
|
|
12
|
+
}
|
|
13
|
+
if (minutes >= 60) {
|
|
14
|
+
const hours = Math.round(minutes / 60);
|
|
15
|
+
return hours === 1 ? t("customers.schedule.reminder.hourBefore", "1 hour before") : t("customers.schedule.reminder.hoursBefore", "{hours} hours before", { hours });
|
|
16
|
+
}
|
|
17
|
+
return t("customers.schedule.reminder.minutesBefore", "{minutes} min before", { minutes });
|
|
18
|
+
}
|
|
7
19
|
function FooterFields({
|
|
8
20
|
visible,
|
|
9
21
|
activityType,
|
|
@@ -27,7 +39,7 @@ function FooterFields({
|
|
|
27
39
|
value: reminderMinutes,
|
|
28
40
|
onChange: (e) => setReminderMinutes(Number(e.target.value)),
|
|
29
41
|
className: "flex-1 appearance-none bg-transparent text-sm text-foreground focus:outline-none",
|
|
30
|
-
children: REMINDER_OPTIONS.map((m) => /* @__PURE__ */ jsx("option", { value: m, children: m
|
|
42
|
+
children: REMINDER_OPTIONS.map((m) => /* @__PURE__ */ jsx("option", { value: m, children: formatReminderLabel(m, t) }, m))
|
|
31
43
|
}
|
|
32
44
|
),
|
|
33
45
|
/* @__PURE__ */ jsx(ChevronDown, { className: "size-3.5 text-muted-foreground" })
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/components/detail/schedule/FooterFields.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport { Bell, Eye, ChevronDown } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { ActivityType, ScheduleFieldId } from './fieldConfig'\nimport { isVisible, getFieldLabel } from './fieldConfig'\n\nconst REMINDER_OPTIONS = [0, 5, 10, 15, 30, 60]\n\ninterface FooterFieldsProps {\n visible: Set<ScheduleFieldId>\n activityType: ActivityType\n reminderMinutes: number\n setReminderMinutes: (value: number) => void\n visibility: string\n setVisibility: (value: string) => void\n}\n\nexport function FooterFields({\n visible,\n activityType,\n reminderMinutes,\n setReminderMinutes,\n visibility,\n setVisibility,\n}: FooterFieldsProps) {\n const t = useT()\n\n const showReminder = isVisible(activityType, 'reminder')\n const showVisibility = isVisible(activityType, 'visibility')\n\n if (!showReminder && !showVisibility) return null\n\n return (\n <div className=\"flex gap-3\">\n {showReminder && (\n <div className=\"flex flex-1 flex-col gap-1.5\">\n <label className=\"text-overline font-semibold text-muted-foreground tracking-wider\">\n {getFieldLabel(activityType, 'reminder', t, 'customers.schedule.reminder', 'Reminder')}\n </label>\n <div className=\"flex items-center gap-2 rounded-md border border-border bg-background px-3 py-2.5\">\n <Bell className=\"size-3.5 text-muted-foreground\" />\n <select\n value={reminderMinutes}\n onChange={(e) => setReminderMinutes(Number(e.target.value))}\n className=\"flex-1 appearance-none bg-transparent text-sm text-foreground focus:outline-none\"\n >\n {REMINDER_OPTIONS.map((m) => (\n <option key={m} value={m}>\n {m
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport { Bell, Eye, ChevronDown } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { ActivityType, ScheduleFieldId } from './fieldConfig'\nimport { isVisible, getFieldLabel } from './fieldConfig'\n\nconst REMINDER_OPTIONS = [0, 5, 10, 15, 30, 60, 240, 1440]\n\nfunction formatReminderLabel(\n minutes: number,\n t: (key: string, fallback: string, params?: Record<string, string | number>) => string,\n): string {\n if (minutes === 0) return t('customers.schedule.reminder.none', 'None')\n if (minutes >= 1440) {\n const days = Math.round(minutes / 1440)\n return days === 1\n ? t('customers.schedule.reminder.dayBefore', '1 day before')\n : t('customers.schedule.reminder.daysBefore', '{days} days before', { days })\n }\n if (minutes >= 60) {\n const hours = Math.round(minutes / 60)\n return hours === 1\n ? t('customers.schedule.reminder.hourBefore', '1 hour before')\n : t('customers.schedule.reminder.hoursBefore', '{hours} hours before', { hours })\n }\n return t('customers.schedule.reminder.minutesBefore', '{minutes} min before', { minutes })\n}\n\ninterface FooterFieldsProps {\n visible: Set<ScheduleFieldId>\n activityType: ActivityType\n reminderMinutes: number\n setReminderMinutes: (value: number) => void\n visibility: string\n setVisibility: (value: string) => void\n}\n\nexport function FooterFields({\n visible,\n activityType,\n reminderMinutes,\n setReminderMinutes,\n visibility,\n setVisibility,\n}: FooterFieldsProps) {\n const t = useT()\n\n const showReminder = isVisible(activityType, 'reminder')\n const showVisibility = isVisible(activityType, 'visibility')\n\n if (!showReminder && !showVisibility) return null\n\n return (\n <div className=\"flex gap-3\">\n {showReminder && (\n <div className=\"flex flex-1 flex-col gap-1.5\">\n <label className=\"text-overline font-semibold text-muted-foreground tracking-wider\">\n {getFieldLabel(activityType, 'reminder', t, 'customers.schedule.reminder', 'Reminder')}\n </label>\n <div className=\"flex items-center gap-2 rounded-md border border-border bg-background px-3 py-2.5\">\n <Bell className=\"size-3.5 text-muted-foreground\" />\n <select\n value={reminderMinutes}\n onChange={(e) => setReminderMinutes(Number(e.target.value))}\n className=\"flex-1 appearance-none bg-transparent text-sm text-foreground focus:outline-none\"\n >\n {REMINDER_OPTIONS.map((m) => (\n <option key={m} value={m}>\n {formatReminderLabel(m, t)}\n </option>\n ))}\n </select>\n <ChevronDown className=\"size-3.5 text-muted-foreground\" />\n </div>\n </div>\n )}\n {showVisibility && (\n <div className=\"flex flex-1 flex-col gap-1.5\">\n <label className=\"text-overline font-semibold text-muted-foreground tracking-wider\">\n {getFieldLabel(activityType, 'visibility', t, 'customers.schedule.visibility', 'Visibility')}\n </label>\n <div className=\"flex items-center gap-2 rounded-md border border-border bg-background px-3 py-2.5\">\n <Eye className=\"size-3.5 text-muted-foreground\" />\n <select\n value={visibility}\n onChange={(e) => setVisibility(e.target.value)}\n className=\"flex-1 appearance-none bg-transparent text-sm text-foreground focus:outline-none\"\n >\n <option value=\"team\">{t('customers.schedule.visibility.team', 'Team only')}</option>\n <option value=\"public\">{t('customers.schedule.visibility.public', 'Public')}</option>\n </select>\n <ChevronDown className=\"size-3.5 text-muted-foreground\" />\n </div>\n </div>\n )}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAyDU,cAGA,YAHA;AAvDV,SAAS,MAAM,KAAK,mBAAmB;AACvC,SAAS,YAAY;AAErB,SAAS,WAAW,qBAAqB;AAEzC,MAAM,mBAAmB,CAAC,GAAG,GAAG,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI;AAEzD,SAAS,oBACP,SACA,GACQ;AACR,MAAI,YAAY,EAAG,QAAO,EAAE,oCAAoC,MAAM;AACtE,MAAI,WAAW,MAAM;AACnB,UAAM,OAAO,KAAK,MAAM,UAAU,IAAI;AACtC,WAAO,SAAS,IACZ,EAAE,yCAAyC,cAAc,IACzD,EAAE,0CAA0C,sBAAsB,EAAE,KAAK,CAAC;AAAA,EAChF;AACA,MAAI,WAAW,IAAI;AACjB,UAAM,QAAQ,KAAK,MAAM,UAAU,EAAE;AACrC,WAAO,UAAU,IACb,EAAE,0CAA0C,eAAe,IAC3D,EAAE,2CAA2C,wBAAwB,EAAE,MAAM,CAAC;AAAA,EACpF;AACA,SAAO,EAAE,6CAA6C,wBAAwB,EAAE,QAAQ,CAAC;AAC3F;AAWO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAsB;AACpB,QAAM,IAAI,KAAK;AAEf,QAAM,eAAe,UAAU,cAAc,UAAU;AACvD,QAAM,iBAAiB,UAAU,cAAc,YAAY;AAE3D,MAAI,CAAC,gBAAgB,CAAC,eAAgB,QAAO;AAE7C,SACE,qBAAC,SAAI,WAAU,cACZ;AAAA,oBACC,qBAAC,SAAI,WAAU,gCACb;AAAA,0BAAC,WAAM,WAAU,oEACd,wBAAc,cAAc,YAAY,GAAG,+BAA+B,UAAU,GACvF;AAAA,MACA,qBAAC,SAAI,WAAU,qFACb;AAAA,4BAAC,QAAK,WAAU,kCAAiC;AAAA,QACjD;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,YACP,UAAU,CAAC,MAAM,mBAAmB,OAAO,EAAE,OAAO,KAAK,CAAC;AAAA,YAC1D,WAAU;AAAA,YAET,2BAAiB,IAAI,CAAC,MACrB,oBAAC,YAAe,OAAO,GACpB,8BAAoB,GAAG,CAAC,KADd,CAEb,CACD;AAAA;AAAA,QACH;AAAA,QACA,oBAAC,eAAY,WAAU,kCAAiC;AAAA,SAC1D;AAAA,OACF;AAAA,IAED,kBACC,qBAAC,SAAI,WAAU,gCACb;AAAA,0BAAC,WAAM,WAAU,oEACd,wBAAc,cAAc,cAAc,GAAG,iCAAiC,YAAY,GAC7F;AAAA,MACA,qBAAC,SAAI,WAAU,qFACb;AAAA,4BAAC,OAAI,WAAU,kCAAiC;AAAA,QAChD;AAAA,UAAC;AAAA;AAAA,YACC,OAAO;AAAA,YACP,UAAU,CAAC,MAAM,cAAc,EAAE,OAAO,KAAK;AAAA,YAC7C,WAAU;AAAA,YAEV;AAAA,kCAAC,YAAO,OAAM,QAAQ,YAAE,sCAAsC,WAAW,GAAE;AAAA,cAC3E,oBAAC,YAAO,OAAM,UAAU,YAAE,wCAAwC,QAAQ,GAAE;AAAA;AAAA;AAAA,QAC9E;AAAA,QACA,oBAAC,eAAY,WAAU,kCAAiC;AAAA,SAC1D;AAAA,OACF;AAAA,KAEJ;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -8,7 +8,7 @@ import { readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
|
|
|
8
8
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
9
9
|
import { IconButton } from "@open-mercato/ui/primitives/icon-button";
|
|
10
10
|
import { Popover, PopoverContent, PopoverTrigger } from "@open-mercato/ui/primitives/popover";
|
|
11
|
-
import { isVisible } from "./fieldConfig.js";
|
|
11
|
+
import { isVisible, getFieldLabel } from "./fieldConfig.js";
|
|
12
12
|
const ENTITY_LINK_TYPES = ["company", "deal", "offer"];
|
|
13
13
|
function readLabelCandidate(value) {
|
|
14
14
|
if (typeof value !== "string") return null;
|
|
@@ -180,8 +180,15 @@ function LinkedEntitiesField({
|
|
|
180
180
|
}) {
|
|
181
181
|
const t = useT();
|
|
182
182
|
if (!isVisible(activityType, "linkedEntities")) return null;
|
|
183
|
+
const sectionLabel = getFieldLabel(
|
|
184
|
+
activityType,
|
|
185
|
+
"linkedEntities",
|
|
186
|
+
t,
|
|
187
|
+
"customers.schedule.linkedEntities",
|
|
188
|
+
"Linked entities"
|
|
189
|
+
);
|
|
183
190
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
184
|
-
/* @__PURE__ */ jsx("label", { className: "text-overline font-semibold uppercase text-muted-foreground tracking-wider", children:
|
|
191
|
+
/* @__PURE__ */ jsx("label", { className: "text-overline font-semibold uppercase text-muted-foreground tracking-wider", children: sectionLabel }),
|
|
185
192
|
/* @__PURE__ */ jsxs("div", { className: "mt-2.5 flex flex-wrap content-center items-center gap-2", children: [
|
|
186
193
|
linkedEntities.map((entity) => /* @__PURE__ */ jsxs(
|
|
187
194
|
"div",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/components/detail/schedule/LinkedEntitiesField.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Building2, Briefcase, FileText, Search, X } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'\nimport type { ActivityType, ScheduleFieldId } from './fieldConfig'\nimport { isVisible } from './fieldConfig'\nimport type { LinkedEntity } from './useScheduleFormState'\n\nconst ENTITY_LINK_TYPES = ['company', 'deal', 'offer'] as const\n\nfunction readLabelCandidate(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nfunction resolveLinkedEntityLabel(\n item: Record<string, unknown>,\n linkType: 'company' | 'deal' | 'offer',\n): string {\n if (linkType === 'offer') {\n const quoteNumber =\n readLabelCandidate(item.quoteNumber)\n ?? readLabelCandidate(item.quote_number)\n ?? readLabelCandidate(item.documentNumber)\n ?? readLabelCandidate(item.document_number)\n ?? readLabelCandidate(item.externalReference)\n ?? readLabelCandidate(item.external_reference)\n const customerName =\n readLabelCandidate(item.customerName)\n ?? readLabelCandidate(item.customer_name)\n ?? readLabelCandidate(item.display_name)\n ?? readLabelCandidate(item.displayName)\n ?? readLabelCandidate(item.name)\n ?? readLabelCandidate(item.title)\n if (quoteNumber && customerName) return `${quoteNumber} \u00B7 ${customerName}`\n if (quoteNumber) return quoteNumber\n if (customerName) return customerName\n }\n\n if (linkType === 'deal') {\n const dealLabel =\n readLabelCandidate(item.title)\n ?? readLabelCandidate(item.name)\n ?? readLabelCandidate(item.display_name)\n ?? readLabelCandidate(item.displayName)\n if (dealLabel) return dealLabel\n }\n\n return (\n readLabelCandidate(item.display_name)\n ?? readLabelCandidate(item.displayName)\n ?? readLabelCandidate(item.name)\n ?? readLabelCandidate(item.title)\n ?? String(item.id ?? '')\n )\n}\n\nfunction EntityLinkSearchPopover({\n existingIds,\n onAdd,\n onAddMany,\n t,\n}: {\n existingIds: Set<string>\n onAdd: (entity: LinkedEntity) => void\n onAddMany: (entities: LinkedEntity[]) => void\n t: (key: string, fallback: string) => string\n}) {\n const [open, setOpen] = React.useState(false)\n const [linkType, setLinkType] = React.useState<'company' | 'deal' | 'offer'>('company')\n const [query, setQuery] = React.useState('')\n const [results, setResults] = React.useState<Array<{ id: string; label: string }>>([])\n const [page, setPage] = React.useState(1)\n const [totalPages, setTotalPages] = React.useState(1)\n const [loading, setLoading] = React.useState(false)\n const selectableResults = React.useMemo(\n () => results.filter((result) => !existingIds.has(result.id)),\n [existingIds, results],\n )\n\n React.useEffect(() => {\n if (!open) return\n const controller = new AbortController()\n setLoading(true)\n const searchParam = query.trim() ? `&search=${encodeURIComponent(query.trim())}` : ''\n const pagingParam = `&page=${page}&pageSize=20`\n const endpoint = linkType === 'company'\n ? `/api/customers/companies?sortField=name&sortDir=asc${pagingParam}${searchParam}`\n : linkType === 'deal'\n ? `/api/customers/deals?pageSize=20&page=${page}${searchParam}`\n : `/api/sales/quotes?pageSize=20&page=${page}${searchParam}`\n readApiResultOrThrow<{ items?: Array<Record<string, unknown>>; totalPages?: number; page?: number; pageSize?: number; total?: number }>(endpoint, { signal: controller.signal })\n .then((data) => {\n const items = Array.isArray(data?.items) ? data.items : []\n const nextResults = items.map((item) => ({\n id: typeof item?.id === 'string' ? item.id : '',\n label: resolveLinkedEntityLabel(item, linkType),\n })).filter((r) => r.id)\n setResults((current) => {\n if (page <= 1) return nextResults\n const merged = new Map(current.map((entry) => [entry.id, entry]))\n nextResults.forEach((entry) => merged.set(entry.id, entry))\n return Array.from(merged.values())\n })\n if (typeof data?.totalPages === 'number') {\n setTotalPages(data.totalPages)\n } else if (typeof data?.total === 'number' && typeof data?.pageSize === 'number') {\n setTotalPages(Math.max(1, Math.ceil(data.total / data.pageSize)))\n } else {\n setTotalPages(1)\n }\n })\n .catch(() => setResults([]))\n .finally(() => setLoading(false))\n return () => controller.abort()\n }, [open, page, query, linkType])\n\n React.useEffect(() => {\n if (!open) return\n setPage(1)\n }, [open, query, linkType])\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" className=\"h-auto inline-flex items-center gap-1.5 rounded-full border border-border bg-background px-2.5 py-1.5 text-xs text-muted-foreground\">\n <span className=\"text-sm\">+</span>\n {t('customers.schedule.addLink', 'Add link')}\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-72 p-2\">\n <div className=\"flex gap-1 mb-2\">\n {ENTITY_LINK_TYPES.map((type) => (\n <Button\n key={type}\n type=\"button\"\n variant={linkType === type ? 'default' : 'ghost'}\n size=\"sm\"\n className=\"h-6 text-xs flex-1\"\n onClick={() => { setLinkType(type as typeof linkType); setQuery('') }}\n >\n {type === 'company' ? <Building2 className=\"mr-1 size-3\" /> : type === 'deal' ? <Briefcase className=\"mr-1 size-3\" /> : <FileText className=\"mr-1 size-3\" />}\n {type === 'company' ? t('customers.schedule.linkType.company', 'Company') : type === 'deal' ? t('customers.schedule.linkType.deal', 'Deal') : t('customers.schedule.linkType.offer', 'Offer')}\n </Button>\n ))}\n </div>\n <div className=\"flex items-center gap-2 rounded-md border bg-background px-2 py-1.5 mb-2\">\n <Search className=\"size-3.5 text-muted-foreground shrink-0\" />\n <input\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={t('customers.schedule.searchEntity', 'Search...')}\n className=\"flex-1 bg-transparent text-sm focus:outline-none\"\n autoFocus\n />\n </div>\n {selectableResults.length ? (\n <div className=\"mb-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"w-full\"\n onClick={() => {\n onAddMany(\n selectableResults.map((result) => ({\n id: result.id,\n type: linkType,\n label: result.label,\n })),\n )\n setOpen(false)\n setQuery('')\n }}\n >\n {t('customers.schedule.addVisibleLinks', 'Add all visible')}\n </Button>\n </div>\n ) : null}\n <div className=\"max-h-48 overflow-y-auto space-y-0.5\">\n {loading && <p className=\"px-2 py-3 text-xs text-muted-foreground text-center\">{t('customers.schedule.searching', 'Searching...')}</p>}\n {!loading && results.length === 0 && <p className=\"px-2 py-3 text-xs text-muted-foreground text-center\">{t('customers.schedule.noResults', 'No results')}</p>}\n {results.map((r) => {\n const alreadyLinked = existingIds.has(r.id)\n return (\n <Button\n key={r.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={alreadyLinked}\n onClick={() => {\n onAdd({ id: r.id, type: linkType, label: r.label })\n setOpen(false)\n setQuery('')\n }}\n className={cn(\n 'h-auto flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors',\n alreadyLinked ? 'opacity-40 cursor-default' : 'hover:bg-accent cursor-pointer',\n )}\n >\n {linkType === 'company' ? <Building2 className=\"size-3.5 text-muted-foreground shrink-0\" /> : linkType === 'deal' ? <Briefcase className=\"size-3.5 text-muted-foreground shrink-0\" /> : <FileText className=\"size-3.5 text-muted-foreground shrink-0\" />}\n <span className=\"min-w-0 flex-1 truncate\">{r.label}</span>\n </Button>\n )\n })}\n {!loading && page < totalPages ? (\n <div className=\"px-2 py-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"w-full\" onClick={() => setPage((current) => current + 1)}>\n {t('customers.schedule.loadMore', 'Load more')}\n </Button>\n </div>\n ) : null}\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\ninterface LinkedEntitiesFieldProps {\n visible: Set<ScheduleFieldId>\n activityType: ActivityType\n linkedEntities: LinkedEntity[]\n setLinkedEntities: React.Dispatch<React.SetStateAction<LinkedEntity[]>>\n}\n\nexport function LinkedEntitiesField({\n visible,\n activityType,\n linkedEntities,\n setLinkedEntities,\n}: LinkedEntitiesFieldProps) {\n const t = useT()\n\n if (!isVisible(activityType, 'linkedEntities')) return null\n\n return (\n <div>\n <label className=\"text-overline font-semibold uppercase text-muted-foreground tracking-wider\">\n {t('customers.schedule.linkedEntities', 'Linked entities')}\n </label>\n <div className=\"mt-2.5 flex flex-wrap content-center items-center gap-2\">\n {linkedEntities.map((entity) => (\n <div\n key={entity.id}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1.5 text-xs',\n entity.type === 'deal'\n ? 'border-status-success-border bg-status-success-bg font-semibold text-foreground'\n : 'border-border bg-muted text-foreground',\n )}\n >\n {entity.type === 'company' ? <Building2 className=\"size-3\" /> : entity.type === 'deal' ? <Briefcase className=\"size-3\" /> : <FileText className=\"size-3\" />}\n {entity.label}\n <IconButton type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setLinkedEntities((prev) => prev.filter((e) => e.id !== entity.id))} className=\"h-auto text-muted-foreground hover:text-foreground p-0\" aria-label={t('customers.schedule.removeLink', 'Remove link')}>\n <X className=\"size-2.5\" />\n </IconButton>\n </div>\n ))}\n <EntityLinkSearchPopover\n existingIds={new Set(linkedEntities.map((e) => e.id))}\n onAdd={(entity) => setLinkedEntities((prev) => [...prev, entity])}\n onAddMany={(entities) => setLinkedEntities((prev) => [...prev, ...entities])}\n t={t}\n />\n </div>\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";AAoIQ,SACE,KADF;AAlIR,YAAY,WAAW;AACvB,SAAS,WAAW,WAAW,UAAU,QAAQ,SAAS;AAC1D,SAAS,UAAU;AACnB,SAAS,YAAY;AACrB,SAAS,4BAA4B;AACrC,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAC3B,SAAS,SAAS,gBAAgB,sBAAsB;AAExD,SAAS,
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Building2, Briefcase, FileText, Search, X } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'\nimport type { ActivityType, ScheduleFieldId } from './fieldConfig'\nimport { isVisible, getFieldLabel } from './fieldConfig'\nimport type { LinkedEntity } from './useScheduleFormState'\n\nconst ENTITY_LINK_TYPES = ['company', 'deal', 'offer'] as const\n\nfunction readLabelCandidate(value: unknown): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : null\n}\n\nfunction resolveLinkedEntityLabel(\n item: Record<string, unknown>,\n linkType: 'company' | 'deal' | 'offer',\n): string {\n if (linkType === 'offer') {\n const quoteNumber =\n readLabelCandidate(item.quoteNumber)\n ?? readLabelCandidate(item.quote_number)\n ?? readLabelCandidate(item.documentNumber)\n ?? readLabelCandidate(item.document_number)\n ?? readLabelCandidate(item.externalReference)\n ?? readLabelCandidate(item.external_reference)\n const customerName =\n readLabelCandidate(item.customerName)\n ?? readLabelCandidate(item.customer_name)\n ?? readLabelCandidate(item.display_name)\n ?? readLabelCandidate(item.displayName)\n ?? readLabelCandidate(item.name)\n ?? readLabelCandidate(item.title)\n if (quoteNumber && customerName) return `${quoteNumber} \u00B7 ${customerName}`\n if (quoteNumber) return quoteNumber\n if (customerName) return customerName\n }\n\n if (linkType === 'deal') {\n const dealLabel =\n readLabelCandidate(item.title)\n ?? readLabelCandidate(item.name)\n ?? readLabelCandidate(item.display_name)\n ?? readLabelCandidate(item.displayName)\n if (dealLabel) return dealLabel\n }\n\n return (\n readLabelCandidate(item.display_name)\n ?? readLabelCandidate(item.displayName)\n ?? readLabelCandidate(item.name)\n ?? readLabelCandidate(item.title)\n ?? String(item.id ?? '')\n )\n}\n\nfunction EntityLinkSearchPopover({\n existingIds,\n onAdd,\n onAddMany,\n t,\n}: {\n existingIds: Set<string>\n onAdd: (entity: LinkedEntity) => void\n onAddMany: (entities: LinkedEntity[]) => void\n t: (key: string, fallback: string) => string\n}) {\n const [open, setOpen] = React.useState(false)\n const [linkType, setLinkType] = React.useState<'company' | 'deal' | 'offer'>('company')\n const [query, setQuery] = React.useState('')\n const [results, setResults] = React.useState<Array<{ id: string; label: string }>>([])\n const [page, setPage] = React.useState(1)\n const [totalPages, setTotalPages] = React.useState(1)\n const [loading, setLoading] = React.useState(false)\n const selectableResults = React.useMemo(\n () => results.filter((result) => !existingIds.has(result.id)),\n [existingIds, results],\n )\n\n React.useEffect(() => {\n if (!open) return\n const controller = new AbortController()\n setLoading(true)\n const searchParam = query.trim() ? `&search=${encodeURIComponent(query.trim())}` : ''\n const pagingParam = `&page=${page}&pageSize=20`\n const endpoint = linkType === 'company'\n ? `/api/customers/companies?sortField=name&sortDir=asc${pagingParam}${searchParam}`\n : linkType === 'deal'\n ? `/api/customers/deals?pageSize=20&page=${page}${searchParam}`\n : `/api/sales/quotes?pageSize=20&page=${page}${searchParam}`\n readApiResultOrThrow<{ items?: Array<Record<string, unknown>>; totalPages?: number; page?: number; pageSize?: number; total?: number }>(endpoint, { signal: controller.signal })\n .then((data) => {\n const items = Array.isArray(data?.items) ? data.items : []\n const nextResults = items.map((item) => ({\n id: typeof item?.id === 'string' ? item.id : '',\n label: resolveLinkedEntityLabel(item, linkType),\n })).filter((r) => r.id)\n setResults((current) => {\n if (page <= 1) return nextResults\n const merged = new Map(current.map((entry) => [entry.id, entry]))\n nextResults.forEach((entry) => merged.set(entry.id, entry))\n return Array.from(merged.values())\n })\n if (typeof data?.totalPages === 'number') {\n setTotalPages(data.totalPages)\n } else if (typeof data?.total === 'number' && typeof data?.pageSize === 'number') {\n setTotalPages(Math.max(1, Math.ceil(data.total / data.pageSize)))\n } else {\n setTotalPages(1)\n }\n })\n .catch(() => setResults([]))\n .finally(() => setLoading(false))\n return () => controller.abort()\n }, [open, page, query, linkType])\n\n React.useEffect(() => {\n if (!open) return\n setPage(1)\n }, [open, query, linkType])\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" className=\"h-auto inline-flex items-center gap-1.5 rounded-full border border-border bg-background px-2.5 py-1.5 text-xs text-muted-foreground\">\n <span className=\"text-sm\">+</span>\n {t('customers.schedule.addLink', 'Add link')}\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-72 p-2\">\n <div className=\"flex gap-1 mb-2\">\n {ENTITY_LINK_TYPES.map((type) => (\n <Button\n key={type}\n type=\"button\"\n variant={linkType === type ? 'default' : 'ghost'}\n size=\"sm\"\n className=\"h-6 text-xs flex-1\"\n onClick={() => { setLinkType(type as typeof linkType); setQuery('') }}\n >\n {type === 'company' ? <Building2 className=\"mr-1 size-3\" /> : type === 'deal' ? <Briefcase className=\"mr-1 size-3\" /> : <FileText className=\"mr-1 size-3\" />}\n {type === 'company' ? t('customers.schedule.linkType.company', 'Company') : type === 'deal' ? t('customers.schedule.linkType.deal', 'Deal') : t('customers.schedule.linkType.offer', 'Offer')}\n </Button>\n ))}\n </div>\n <div className=\"flex items-center gap-2 rounded-md border bg-background px-2 py-1.5 mb-2\">\n <Search className=\"size-3.5 text-muted-foreground shrink-0\" />\n <input\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={t('customers.schedule.searchEntity', 'Search...')}\n className=\"flex-1 bg-transparent text-sm focus:outline-none\"\n autoFocus\n />\n </div>\n {selectableResults.length ? (\n <div className=\"mb-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"w-full\"\n onClick={() => {\n onAddMany(\n selectableResults.map((result) => ({\n id: result.id,\n type: linkType,\n label: result.label,\n })),\n )\n setOpen(false)\n setQuery('')\n }}\n >\n {t('customers.schedule.addVisibleLinks', 'Add all visible')}\n </Button>\n </div>\n ) : null}\n <div className=\"max-h-48 overflow-y-auto space-y-0.5\">\n {loading && <p className=\"px-2 py-3 text-xs text-muted-foreground text-center\">{t('customers.schedule.searching', 'Searching...')}</p>}\n {!loading && results.length === 0 && <p className=\"px-2 py-3 text-xs text-muted-foreground text-center\">{t('customers.schedule.noResults', 'No results')}</p>}\n {results.map((r) => {\n const alreadyLinked = existingIds.has(r.id)\n return (\n <Button\n key={r.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={alreadyLinked}\n onClick={() => {\n onAdd({ id: r.id, type: linkType, label: r.label })\n setOpen(false)\n setQuery('')\n }}\n className={cn(\n 'h-auto flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors',\n alreadyLinked ? 'opacity-40 cursor-default' : 'hover:bg-accent cursor-pointer',\n )}\n >\n {linkType === 'company' ? <Building2 className=\"size-3.5 text-muted-foreground shrink-0\" /> : linkType === 'deal' ? <Briefcase className=\"size-3.5 text-muted-foreground shrink-0\" /> : <FileText className=\"size-3.5 text-muted-foreground shrink-0\" />}\n <span className=\"min-w-0 flex-1 truncate\">{r.label}</span>\n </Button>\n )\n })}\n {!loading && page < totalPages ? (\n <div className=\"px-2 py-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"w-full\" onClick={() => setPage((current) => current + 1)}>\n {t('customers.schedule.loadMore', 'Load more')}\n </Button>\n </div>\n ) : null}\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\ninterface LinkedEntitiesFieldProps {\n visible: Set<ScheduleFieldId>\n activityType: ActivityType\n linkedEntities: LinkedEntity[]\n setLinkedEntities: React.Dispatch<React.SetStateAction<LinkedEntity[]>>\n}\n\nexport function LinkedEntitiesField({\n visible,\n activityType,\n linkedEntities,\n setLinkedEntities,\n}: LinkedEntitiesFieldProps) {\n const t = useT()\n\n if (!isVisible(activityType, 'linkedEntities')) return null\n\n const sectionLabel = getFieldLabel(\n activityType,\n 'linkedEntities',\n t,\n 'customers.schedule.linkedEntities',\n 'Linked entities',\n )\n\n return (\n <div>\n <label className=\"text-overline font-semibold uppercase text-muted-foreground tracking-wider\">\n {sectionLabel}\n </label>\n <div className=\"mt-2.5 flex flex-wrap content-center items-center gap-2\">\n {linkedEntities.map((entity) => (\n <div\n key={entity.id}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1.5 text-xs',\n entity.type === 'deal'\n ? 'border-status-success-border bg-status-success-bg font-semibold text-foreground'\n : 'border-border bg-muted text-foreground',\n )}\n >\n {entity.type === 'company' ? <Building2 className=\"size-3\" /> : entity.type === 'deal' ? <Briefcase className=\"size-3\" /> : <FileText className=\"size-3\" />}\n {entity.label}\n <IconButton type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => setLinkedEntities((prev) => prev.filter((e) => e.id !== entity.id))} className=\"h-auto text-muted-foreground hover:text-foreground p-0\" aria-label={t('customers.schedule.removeLink', 'Remove link')}>\n <X className=\"size-2.5\" />\n </IconButton>\n </div>\n ))}\n <EntityLinkSearchPopover\n existingIds={new Set(linkedEntities.map((e) => e.id))}\n onAdd={(entity) => setLinkedEntities((prev) => [...prev, entity])}\n onAddMany={(entities) => setLinkedEntities((prev) => [...prev, ...entities])}\n t={t}\n />\n </div>\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -8,7 +8,7 @@ import { Button } from "@open-mercato/ui/primitives/button";
|
|
|
8
8
|
import { IconButton } from "@open-mercato/ui/primitives/icon-button";
|
|
9
9
|
import { Popover, PopoverContent, PopoverTrigger } from "@open-mercato/ui/primitives/popover";
|
|
10
10
|
import { fetchAssignableStaffMembersPage } from "../assignableStaff.js";
|
|
11
|
-
import { isVisible } from "./fieldConfig.js";
|
|
11
|
+
import { isVisible, getFieldLabel } from "./fieldConfig.js";
|
|
12
12
|
import { PARTICIPANT_COLORS } from "./useScheduleFormState.js";
|
|
13
13
|
function ParticipantSearchPopover({
|
|
14
14
|
existingIds,
|
|
@@ -150,8 +150,15 @@ function ParticipantsField({
|
|
|
150
150
|
}) {
|
|
151
151
|
const t = useT();
|
|
152
152
|
if (!isVisible(activityType, "participants")) return null;
|
|
153
|
+
const sectionLabel = getFieldLabel(
|
|
154
|
+
activityType,
|
|
155
|
+
"participants",
|
|
156
|
+
t,
|
|
157
|
+
"customers.schedule.participants",
|
|
158
|
+
"Participants"
|
|
159
|
+
);
|
|
153
160
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
154
|
-
/* @__PURE__ */ jsx("label", { className: "text-overline font-semibold uppercase text-muted-foreground tracking-wider", children:
|
|
161
|
+
/* @__PURE__ */ jsx("label", { className: "text-overline font-semibold uppercase text-muted-foreground tracking-wider", children: sectionLabel }),
|
|
155
162
|
/* @__PURE__ */ jsxs("div", { className: "mt-2.5 flex flex-wrap content-center items-center gap-2 rounded-lg border border-border bg-background px-3 py-2.5", children: [
|
|
156
163
|
participants.map((p) => /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-muted px-2.5 py-1.5", children: [
|
|
157
164
|
/* @__PURE__ */ jsx("span", { className: cn("inline-flex size-5 items-center justify-center rounded-full text-xs font-bold text-white", p.color ?? "bg-primary"), children: p.name.charAt(0).toUpperCase() }),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/components/detail/schedule/ParticipantsField.tsx"],
|
|
4
|
-
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Users, Search, X, Clock, CheckCircle2, XCircle } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'\nimport { fetchAssignableStaffMembersPage } from '../assignableStaff'\nimport type { ActivityType, ScheduleFieldId } from './fieldConfig'\nimport { isVisible } from './fieldConfig'\nimport type { Participant, RsvpStatus } from './useScheduleFormState'\nimport { PARTICIPANT_COLORS } from './useScheduleFormState'\n\nfunction ParticipantSearchPopover({\n existingIds,\n onAdd,\n onAddMany,\n t,\n}: {\n existingIds: Set<string>\n onAdd: (p: Participant) => void\n onAddMany: (participants: Participant[]) => void\n t: (key: string, fallback: string) => string\n}) {\n const [open, setOpen] = React.useState(false)\n const [query, setQuery] = React.useState('')\n const [results, setResults] = React.useState<Array<{ userId: string; name: string; email: string }>>([])\n const [page, setPage] = React.useState(1)\n const [totalPages, setTotalPages] = React.useState(1)\n const [loading, setLoading] = React.useState(false)\n const [loadError, setLoadError] = React.useState<string | null>(null)\n const selectableResults = React.useMemo(\n () => results.filter((result) => !existingIds.has(result.userId)),\n [existingIds, results],\n )\n\n React.useEffect(() => {\n if (!open) return\n const controller = new AbortController()\n setLoading(true)\n fetchAssignableStaffMembersPage(query, { page, pageSize: 20, signal: controller.signal })\n .then((result) => {\n const members = result.items\n const nextResults = members.map((member) => ({\n userId: member.userId,\n name: member.displayName,\n email: member.email ?? '',\n }))\n setResults((current) => {\n if (page <= 1) return nextResults\n const merged = new Map(current.map((entry) => [entry.userId, entry]))\n nextResults.forEach((entry) => merged.set(entry.userId, entry))\n return Array.from(merged.values())\n })\n setTotalPages(result.total > 0 ? Math.max(1, Math.ceil(result.total / result.pageSize)) : 1)\n setLoadError(null)\n })\n .catch(() => {\n setResults([])\n setLoadError(\n t(\n 'customers.assignableStaff.loadError',\n 'Unable to load team members. Check your permissions and try again.',\n ),\n )\n })\n .finally(() => setLoading(false))\n return () => controller.abort()\n }, [open, page, query, t])\n\n React.useEffect(() => {\n if (!open) return\n setPage(1)\n }, [open, query])\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" className=\"h-auto inline-flex items-center gap-1.5 rounded-full border border-status-success-border bg-status-success-bg px-2.5 py-1.5 text-xs font-semibold text-foreground\">\n <Users className=\"size-3\" />\n {t('customers.schedule.addParticipant', 'Add participant')}\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-72 p-2\">\n <div className=\"flex items-center gap-2 rounded-md border bg-background px-2 py-1.5 mb-2\">\n <Search className=\"size-3.5 text-muted-foreground shrink-0\" />\n <input\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={t('customers.schedule.searchParticipant', 'Search team members...')}\n className=\"flex-1 bg-transparent text-sm focus:outline-none\"\n autoFocus\n />\n </div>\n {selectableResults.length ? (\n <div className=\"mb-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"w-full\"\n onClick={() => {\n onAddMany(\n selectableResults.map((participant, index) => ({\n userId: participant.userId,\n name: participant.name,\n email: participant.email,\n color: PARTICIPANT_COLORS[(existingIds.size + index) % PARTICIPANT_COLORS.length],\n })),\n )\n setOpen(false)\n setQuery('')\n }}\n >\n {t('customers.schedule.addVisibleParticipants', 'Add all visible')}\n </Button>\n </div>\n ) : null}\n <div className=\"max-h-48 overflow-y-auto space-y-0.5\">\n {loading && <p className=\"px-2 py-3 text-xs text-muted-foreground text-center\">{t('customers.schedule.searching', 'Searching...')}</p>}\n {!loading && loadError && <p className=\"px-2 py-3 text-xs text-destructive text-center\">{loadError}</p>}\n {!loading && !loadError && results.length === 0 && <p className=\"px-2 py-3 text-xs text-muted-foreground text-center\">{t('customers.schedule.noResults', 'No results')}</p>}\n {results.map((r) => {\n const alreadyAdded = existingIds.has(r.userId)\n return (\n <Button\n key={r.userId}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={alreadyAdded}\n onClick={() => {\n onAdd({ userId: r.userId, name: r.name, email: r.email, color: PARTICIPANT_COLORS[existingIds.size % PARTICIPANT_COLORS.length] })\n setOpen(false)\n setQuery('')\n }}\n className={cn(\n 'h-auto flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors',\n alreadyAdded ? 'opacity-40 cursor-default' : 'hover:bg-accent cursor-pointer',\n )}\n >\n <span className=\"inline-flex size-6 items-center justify-center rounded-full bg-muted text-xs font-bold shrink-0\">\n {r.name.charAt(0).toUpperCase()}\n </span>\n <span className=\"min-w-0 flex-1 truncate\">{r.name}</span>\n {r.email && <span className=\"text-xs text-muted-foreground truncate\">{r.email}</span>}\n </Button>\n )\n })}\n {!loading && !loadError && page < totalPages ? (\n <div className=\"px-2 py-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"w-full\" onClick={() => setPage((current) => current + 1)}>\n {t('customers.schedule.loadMore', 'Load more')}\n </Button>\n </div>\n ) : null}\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\ninterface ParticipantsFieldProps {\n visible: Set<ScheduleFieldId>\n activityType: ActivityType\n participants: Participant[]\n setParticipants: React.Dispatch<React.SetStateAction<Participant[]>>\n removeParticipant: (userId: string) => void\n guestPermissions: { canInviteOthers: boolean; canModify: boolean; canSeeList: boolean }\n setGuestPermissions: React.Dispatch<React.SetStateAction<{ canInviteOthers: boolean; canModify: boolean; canSeeList: boolean }>>\n}\n\nexport function ParticipantsField({\n visible,\n activityType,\n participants,\n setParticipants,\n removeParticipant,\n guestPermissions,\n setGuestPermissions,\n}: ParticipantsFieldProps) {\n const t = useT()\n\n if (!isVisible(activityType, 'participants')) return null\n\n return (\n <div>\n <label className=\"text-overline font-semibold uppercase text-muted-foreground tracking-wider\">\n {t('customers.schedule.participants', 'Participants')}\n </label>\n <div className=\"mt-2.5 flex flex-wrap content-center items-center gap-2 rounded-lg border border-border bg-background px-3 py-2.5\">\n {participants.map((p) => (\n <div key={p.userId} className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-muted px-2.5 py-1.5\">\n <span className={cn('inline-flex size-5 items-center justify-center rounded-full text-xs font-bold text-white', p.color ?? 'bg-primary')}>\n {p.name.charAt(0).toUpperCase()}\n </span>\n <span className=\"text-xs text-foreground\">{p.name}</span>\n <IconButton type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => removeParticipant(p.userId)} className=\"h-auto text-muted-foreground hover:text-foreground p-0\" aria-label={t('customers.schedule.removeParticipant', 'Remove participant')}>\n <X className=\"size-3\" />\n </IconButton>\n </div>\n ))}\n <ParticipantSearchPopover\n existingIds={new Set(participants.map((p) => p.userId))}\n onAdd={(p) => setParticipants((prev) => [...prev, { ...p, status: 'pending' as RsvpStatus }])}\n onAddMany={(nextParticipants) => {\n setParticipants((prev) => [\n ...prev,\n ...nextParticipants.map((participant) => ({ ...participant, status: 'pending' as RsvpStatus })),\n ])\n }}\n t={t}\n />\n </div>\n\n {/* Guest permissions -- shown when participants exist */}\n {participants.length > 0 && (\n <div className=\"mt-3 flex flex-wrap items-center gap-x-[16px] gap-y-[6px] text-xs\">\n <span className=\"font-medium text-muted-foreground\">{t('customers.schedule.guestPermissions', 'Guest permissions:')}</span>\n <label className=\"flex items-center gap-1.5 cursor-pointer\">\n <input type=\"checkbox\" checked={guestPermissions.canInviteOthers} onChange={(e) => setGuestPermissions((p) => ({ ...p, canInviteOthers: e.target.checked }))} className=\"rounded\" />\n {t('customers.schedule.guestPerm.invite', 'Invite others')}\n </label>\n <label className=\"flex items-center gap-1.5 cursor-pointer\">\n <input type=\"checkbox\" checked={guestPermissions.canModify} onChange={(e) => setGuestPermissions((p) => ({ ...p, canModify: e.target.checked }))} className=\"rounded\" />\n {t('customers.schedule.guestPerm.modify', 'Modify')}\n </label>\n <label className=\"flex items-center gap-1.5 cursor-pointer\">\n <input type=\"checkbox\" checked={guestPermissions.canSeeList} onChange={(e) => setGuestPermissions((p) => ({ ...p, canSeeList: e.target.checked }))} className=\"rounded\" />\n {t('customers.schedule.guestPerm.seeList', 'See list')}\n </label>\n </div>\n )}\n\n {/* RSVP summary -- shown when participants exist */}\n {participants.length > 0 && (() => {\n const accepted = participants.filter((p) => p.status === 'accepted').length\n const pending = participants.filter((p) => !p.status || p.status === 'pending').length\n const declined = participants.filter((p) => p.status === 'declined').length\n if (accepted === 0 && pending === 0 && declined === 0) return null\n return (\n <div className=\"mt-2 flex items-center gap-3 text-xs\">\n <span className=\"text-muted-foreground\">{t('customers.schedule.rsvp.label', 'Responses:')}</span>\n {accepted > 0 && <span className=\"flex items-center gap-1 font-medium text-status-success-text\"><CheckCircle2 className=\"size-3.5\" /> {accepted} {t('customers.schedule.rsvp.accepted', 'tak')}</span>}\n {pending > 0 && <span className=\"flex items-center gap-1 font-medium text-status-warning-text\"><Clock className=\"size-3.5\" /> {pending} {t('customers.schedule.rsvp.pending', 'czeka')}</span>}\n {declined > 0 && <span className=\"flex items-center gap-1 font-medium text-status-error-text\"><XCircle className=\"size-3.5\" /> {declined} {t('customers.schedule.rsvp.declined', 'nie')}</span>}\n </div>\n )\n })()}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";AAgFQ,SACE,KADF;AA9ER,YAAY,WAAW;AACvB,SAAS,OAAO,QAAQ,GAAG,OAAO,cAAc,eAAe;AAC/D,SAAS,UAAU;AACnB,SAAS,YAAY;AACrB,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAC3B,SAAS,SAAS,gBAAgB,sBAAsB;AACxD,SAAS,uCAAuC;AAEhD,SAAS,
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Users, Search, X, Clock, CheckCircle2, XCircle } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'\nimport { fetchAssignableStaffMembersPage } from '../assignableStaff'\nimport type { ActivityType, ScheduleFieldId } from './fieldConfig'\nimport { isVisible, getFieldLabel } from './fieldConfig'\nimport type { Participant, RsvpStatus } from './useScheduleFormState'\nimport { PARTICIPANT_COLORS } from './useScheduleFormState'\n\nfunction ParticipantSearchPopover({\n existingIds,\n onAdd,\n onAddMany,\n t,\n}: {\n existingIds: Set<string>\n onAdd: (p: Participant) => void\n onAddMany: (participants: Participant[]) => void\n t: (key: string, fallback: string) => string\n}) {\n const [open, setOpen] = React.useState(false)\n const [query, setQuery] = React.useState('')\n const [results, setResults] = React.useState<Array<{ userId: string; name: string; email: string }>>([])\n const [page, setPage] = React.useState(1)\n const [totalPages, setTotalPages] = React.useState(1)\n const [loading, setLoading] = React.useState(false)\n const [loadError, setLoadError] = React.useState<string | null>(null)\n const selectableResults = React.useMemo(\n () => results.filter((result) => !existingIds.has(result.userId)),\n [existingIds, results],\n )\n\n React.useEffect(() => {\n if (!open) return\n const controller = new AbortController()\n setLoading(true)\n fetchAssignableStaffMembersPage(query, { page, pageSize: 20, signal: controller.signal })\n .then((result) => {\n const members = result.items\n const nextResults = members.map((member) => ({\n userId: member.userId,\n name: member.displayName,\n email: member.email ?? '',\n }))\n setResults((current) => {\n if (page <= 1) return nextResults\n const merged = new Map(current.map((entry) => [entry.userId, entry]))\n nextResults.forEach((entry) => merged.set(entry.userId, entry))\n return Array.from(merged.values())\n })\n setTotalPages(result.total > 0 ? Math.max(1, Math.ceil(result.total / result.pageSize)) : 1)\n setLoadError(null)\n })\n .catch(() => {\n setResults([])\n setLoadError(\n t(\n 'customers.assignableStaff.loadError',\n 'Unable to load team members. Check your permissions and try again.',\n ),\n )\n })\n .finally(() => setLoading(false))\n return () => controller.abort()\n }, [open, page, query, t])\n\n React.useEffect(() => {\n if (!open) return\n setPage(1)\n }, [open, query])\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" className=\"h-auto inline-flex items-center gap-1.5 rounded-full border border-status-success-border bg-status-success-bg px-2.5 py-1.5 text-xs font-semibold text-foreground\">\n <Users className=\"size-3\" />\n {t('customers.schedule.addParticipant', 'Add participant')}\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-72 p-2\">\n <div className=\"flex items-center gap-2 rounded-md border bg-background px-2 py-1.5 mb-2\">\n <Search className=\"size-3.5 text-muted-foreground shrink-0\" />\n <input\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder={t('customers.schedule.searchParticipant', 'Search team members...')}\n className=\"flex-1 bg-transparent text-sm focus:outline-none\"\n autoFocus\n />\n </div>\n {selectableResults.length ? (\n <div className=\"mb-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"w-full\"\n onClick={() => {\n onAddMany(\n selectableResults.map((participant, index) => ({\n userId: participant.userId,\n name: participant.name,\n email: participant.email,\n color: PARTICIPANT_COLORS[(existingIds.size + index) % PARTICIPANT_COLORS.length],\n })),\n )\n setOpen(false)\n setQuery('')\n }}\n >\n {t('customers.schedule.addVisibleParticipants', 'Add all visible')}\n </Button>\n </div>\n ) : null}\n <div className=\"max-h-48 overflow-y-auto space-y-0.5\">\n {loading && <p className=\"px-2 py-3 text-xs text-muted-foreground text-center\">{t('customers.schedule.searching', 'Searching...')}</p>}\n {!loading && loadError && <p className=\"px-2 py-3 text-xs text-destructive text-center\">{loadError}</p>}\n {!loading && !loadError && results.length === 0 && <p className=\"px-2 py-3 text-xs text-muted-foreground text-center\">{t('customers.schedule.noResults', 'No results')}</p>}\n {results.map((r) => {\n const alreadyAdded = existingIds.has(r.userId)\n return (\n <Button\n key={r.userId}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={alreadyAdded}\n onClick={() => {\n onAdd({ userId: r.userId, name: r.name, email: r.email, color: PARTICIPANT_COLORS[existingIds.size % PARTICIPANT_COLORS.length] })\n setOpen(false)\n setQuery('')\n }}\n className={cn(\n 'h-auto flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors',\n alreadyAdded ? 'opacity-40 cursor-default' : 'hover:bg-accent cursor-pointer',\n )}\n >\n <span className=\"inline-flex size-6 items-center justify-center rounded-full bg-muted text-xs font-bold shrink-0\">\n {r.name.charAt(0).toUpperCase()}\n </span>\n <span className=\"min-w-0 flex-1 truncate\">{r.name}</span>\n {r.email && <span className=\"text-xs text-muted-foreground truncate\">{r.email}</span>}\n </Button>\n )\n })}\n {!loading && !loadError && page < totalPages ? (\n <div className=\"px-2 py-2\">\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"w-full\" onClick={() => setPage((current) => current + 1)}>\n {t('customers.schedule.loadMore', 'Load more')}\n </Button>\n </div>\n ) : null}\n </div>\n </PopoverContent>\n </Popover>\n )\n}\n\ninterface ParticipantsFieldProps {\n visible: Set<ScheduleFieldId>\n activityType: ActivityType\n participants: Participant[]\n setParticipants: React.Dispatch<React.SetStateAction<Participant[]>>\n removeParticipant: (userId: string) => void\n guestPermissions: { canInviteOthers: boolean; canModify: boolean; canSeeList: boolean }\n setGuestPermissions: React.Dispatch<React.SetStateAction<{ canInviteOthers: boolean; canModify: boolean; canSeeList: boolean }>>\n}\n\nexport function ParticipantsField({\n visible,\n activityType,\n participants,\n setParticipants,\n removeParticipant,\n guestPermissions,\n setGuestPermissions,\n}: ParticipantsFieldProps) {\n const t = useT()\n\n if (!isVisible(activityType, 'participants')) return null\n\n const sectionLabel = getFieldLabel(\n activityType,\n 'participants',\n t,\n 'customers.schedule.participants',\n 'Participants',\n )\n\n return (\n <div>\n <label className=\"text-overline font-semibold uppercase text-muted-foreground tracking-wider\">\n {sectionLabel}\n </label>\n <div className=\"mt-2.5 flex flex-wrap content-center items-center gap-2 rounded-lg border border-border bg-background px-3 py-2.5\">\n {participants.map((p) => (\n <div key={p.userId} className=\"inline-flex items-center gap-1.5 rounded-full border border-border bg-muted px-2.5 py-1.5\">\n <span className={cn('inline-flex size-5 items-center justify-center rounded-full text-xs font-bold text-white', p.color ?? 'bg-primary')}>\n {p.name.charAt(0).toUpperCase()}\n </span>\n <span className=\"text-xs text-foreground\">{p.name}</span>\n <IconButton type=\"button\" variant=\"ghost\" size=\"sm\" onClick={() => removeParticipant(p.userId)} className=\"h-auto text-muted-foreground hover:text-foreground p-0\" aria-label={t('customers.schedule.removeParticipant', 'Remove participant')}>\n <X className=\"size-3\" />\n </IconButton>\n </div>\n ))}\n <ParticipantSearchPopover\n existingIds={new Set(participants.map((p) => p.userId))}\n onAdd={(p) => setParticipants((prev) => [...prev, { ...p, status: 'pending' as RsvpStatus }])}\n onAddMany={(nextParticipants) => {\n setParticipants((prev) => [\n ...prev,\n ...nextParticipants.map((participant) => ({ ...participant, status: 'pending' as RsvpStatus })),\n ])\n }}\n t={t}\n />\n </div>\n\n {/* Guest permissions -- shown when participants exist */}\n {participants.length > 0 && (\n <div className=\"mt-3 flex flex-wrap items-center gap-x-[16px] gap-y-[6px] text-xs\">\n <span className=\"font-medium text-muted-foreground\">{t('customers.schedule.guestPermissions', 'Guest permissions:')}</span>\n <label className=\"flex items-center gap-1.5 cursor-pointer\">\n <input type=\"checkbox\" checked={guestPermissions.canInviteOthers} onChange={(e) => setGuestPermissions((p) => ({ ...p, canInviteOthers: e.target.checked }))} className=\"rounded\" />\n {t('customers.schedule.guestPerm.invite', 'Invite others')}\n </label>\n <label className=\"flex items-center gap-1.5 cursor-pointer\">\n <input type=\"checkbox\" checked={guestPermissions.canModify} onChange={(e) => setGuestPermissions((p) => ({ ...p, canModify: e.target.checked }))} className=\"rounded\" />\n {t('customers.schedule.guestPerm.modify', 'Modify')}\n </label>\n <label className=\"flex items-center gap-1.5 cursor-pointer\">\n <input type=\"checkbox\" checked={guestPermissions.canSeeList} onChange={(e) => setGuestPermissions((p) => ({ ...p, canSeeList: e.target.checked }))} className=\"rounded\" />\n {t('customers.schedule.guestPerm.seeList', 'See list')}\n </label>\n </div>\n )}\n\n {/* RSVP summary -- shown when participants exist */}\n {participants.length > 0 && (() => {\n const accepted = participants.filter((p) => p.status === 'accepted').length\n const pending = participants.filter((p) => !p.status || p.status === 'pending').length\n const declined = participants.filter((p) => p.status === 'declined').length\n if (accepted === 0 && pending === 0 && declined === 0) return null\n return (\n <div className=\"mt-2 flex items-center gap-3 text-xs\">\n <span className=\"text-muted-foreground\">{t('customers.schedule.rsvp.label', 'Responses:')}</span>\n {accepted > 0 && <span className=\"flex items-center gap-1 font-medium text-status-success-text\"><CheckCircle2 className=\"size-3.5\" /> {accepted} {t('customers.schedule.rsvp.accepted', 'tak')}</span>}\n {pending > 0 && <span className=\"flex items-center gap-1 font-medium text-status-warning-text\"><Clock className=\"size-3.5\" /> {pending} {t('customers.schedule.rsvp.pending', 'czeka')}</span>}\n {declined > 0 && <span className=\"flex items-center gap-1 font-medium text-status-error-text\"><XCircle className=\"size-3.5\" /> {declined} {t('customers.schedule.rsvp.declined', 'nie')}</span>}\n </div>\n )\n })()}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -26,30 +26,51 @@ const FIELD_VISIBILITY = {
|
|
|
26
26
|
"reminder",
|
|
27
27
|
"visibility"
|
|
28
28
|
]),
|
|
29
|
+
// Task: now also surfaces Due time (startTime) + Estimate (duration) per Figma 790:280.
|
|
29
30
|
task: /* @__PURE__ */ new Set([
|
|
30
31
|
"title",
|
|
31
32
|
"date",
|
|
33
|
+
"startTime",
|
|
34
|
+
"duration",
|
|
32
35
|
"linkedEntities",
|
|
33
36
|
"description",
|
|
34
37
|
"reminder",
|
|
35
38
|
"visibility"
|
|
36
39
|
]),
|
|
40
|
+
// Email: surface participants as TO recipients per Figma 790:510.
|
|
37
41
|
email: /* @__PURE__ */ new Set([
|
|
38
42
|
"title",
|
|
39
43
|
"date",
|
|
40
44
|
"startTime",
|
|
45
|
+
"participants",
|
|
41
46
|
"linkedEntities",
|
|
42
47
|
"description",
|
|
48
|
+
"reminder",
|
|
43
49
|
"visibility"
|
|
44
50
|
])
|
|
45
51
|
};
|
|
46
52
|
const FIELD_LABEL_OVERRIDES = {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
meeting: {
|
|
54
|
+
participants: { key: "customers.schedule.attendees", fallback: "Attendees" },
|
|
55
|
+
linkedEntities: { key: "customers.schedule.connections", fallback: "Connections" }
|
|
56
|
+
},
|
|
57
|
+
call: {
|
|
58
|
+
participants: { key: "customers.schedule.contact", fallback: "Contact" },
|
|
59
|
+
linkedEntities: { key: "customers.schedule.connections", fallback: "Connections" },
|
|
60
|
+
description: { key: "customers.schedule.callNotes", fallback: "Call notes" }
|
|
50
61
|
},
|
|
51
62
|
task: {
|
|
52
|
-
date: { key: "customers.schedule.dueDate", fallback: "Due date" }
|
|
63
|
+
date: { key: "customers.schedule.dueDate", fallback: "Due date" },
|
|
64
|
+
startTime: { key: "customers.schedule.dueTime", fallback: "Due time" },
|
|
65
|
+
duration: { key: "customers.schedule.estimate", fallback: "Estimate" },
|
|
66
|
+
linkedEntities: { key: "customers.schedule.connections", fallback: "Connections" },
|
|
67
|
+
description: { key: "customers.schedule.details", fallback: "Details" }
|
|
68
|
+
},
|
|
69
|
+
email: {
|
|
70
|
+
title: { key: "customers.schedule.subject", fallback: "Subject" },
|
|
71
|
+
participants: { key: "customers.schedule.to", fallback: "To" },
|
|
72
|
+
linkedEntities: { key: "customers.schedule.connections", fallback: "Connections" },
|
|
73
|
+
description: { key: "customers.schedule.message", fallback: "Message" }
|
|
53
74
|
}
|
|
54
75
|
};
|
|
55
76
|
function isVisible(type, fieldId) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/components/detail/schedule/fieldConfig.ts"],
|
|
4
|
-
"sourcesContent": ["export type ActivityType = 'meeting' | 'call' | 'task' | 'email'\n\nexport type ScheduleFieldId =\n | 'title'\n | 'date'\n | 'startTime'\n | 'duration'\n | 'allDay'\n | 'timezone'\n | 'recurrence'\n | 'participants'\n | 'guestPermissions'\n | 'location'\n | 'linkedEntities'\n | 'description'\n | 'reminder'\n | 'visibility'\n\nexport const FIELD_VISIBILITY: Record<ActivityType, Set<ScheduleFieldId>> = {\n meeting: new Set([\n 'title', 'date', 'startTime', 'duration', 'allDay', 'timezone', 'recurrence',\n 'participants', 'guestPermissions', 'location', 'linkedEntities', 'description',\n 'reminder', 'visibility',\n ]),\n call: new Set([\n 'title', 'date', 'startTime', 'duration',\n 'participants', 'linkedEntities', 'description',\n 'reminder', 'visibility',\n ]),\n task: new Set([\n 'title', 'date',\n 'linkedEntities', 'description',\n 'reminder', 'visibility',\n ]),\n email: new Set([\n 'title', 'date', 'startTime',\n 'linkedEntities', 'description',\n 'visibility',\n ]),\n}\n\ntype LabelOverride = { key: string; fallback: string }\n\nexport const FIELD_LABEL_OVERRIDES: Partial<\n Record<ActivityType, Partial<Record<ScheduleFieldId, LabelOverride>>>\n> = {\n
|
|
5
|
-
"mappings": "AAkBO,MAAM,mBAA+D;AAAA,EAC1E,SAAS,oBAAI,IAAI;AAAA,IACf;AAAA,IAAS;AAAA,IAAQ;AAAA,IAAa;AAAA,IAAY;AAAA,IAAU;AAAA,IAAY;AAAA,IAChE;AAAA,IAAgB;AAAA,IAAoB;AAAA,IAAY;AAAA,IAAkB;AAAA,IAClE;AAAA,IAAY;AAAA,EACd,CAAC;AAAA,EACD,MAAM,oBAAI,IAAI;AAAA,IACZ;AAAA,IAAS;AAAA,IAAQ;AAAA,IAAa;AAAA,IAC9B;AAAA,IAAgB;AAAA,IAAkB;AAAA,IAClC;AAAA,IAAY;AAAA,EACd,CAAC;AAAA,
|
|
4
|
+
"sourcesContent": ["export type ActivityType = 'meeting' | 'call' | 'task' | 'email'\n\nexport type ScheduleFieldId =\n | 'title'\n | 'date'\n | 'startTime'\n | 'duration'\n | 'allDay'\n | 'timezone'\n | 'recurrence'\n | 'participants'\n | 'guestPermissions'\n | 'location'\n | 'linkedEntities'\n | 'description'\n | 'reminder'\n | 'visibility'\n\nexport const FIELD_VISIBILITY: Record<ActivityType, Set<ScheduleFieldId>> = {\n meeting: new Set([\n 'title', 'date', 'startTime', 'duration', 'allDay', 'timezone', 'recurrence',\n 'participants', 'guestPermissions', 'location', 'linkedEntities', 'description',\n 'reminder', 'visibility',\n ]),\n call: new Set([\n 'title', 'date', 'startTime', 'duration',\n 'participants', 'linkedEntities', 'description',\n 'reminder', 'visibility',\n ]),\n // Task: now also surfaces Due time (startTime) + Estimate (duration) per Figma 790:280.\n task: new Set([\n 'title', 'date', 'startTime', 'duration',\n 'linkedEntities', 'description',\n 'reminder', 'visibility',\n ]),\n // Email: surface participants as TO recipients per Figma 790:510.\n email: new Set([\n 'title', 'date', 'startTime',\n 'participants', 'linkedEntities', 'description',\n 'reminder', 'visibility',\n ]),\n}\n\ntype LabelOverride = { key: string; fallback: string }\n\n// Per-type section labels (Figma 784:1255 / 829:50 / 790:280 / 790:510).\n// `participants` / `linkedEntities` / `description` resolve via these overrides\n// when present; otherwise the field components fall back to their generic key.\nexport const FIELD_LABEL_OVERRIDES: Partial<\n Record<ActivityType, Partial<Record<ScheduleFieldId, LabelOverride>>>\n> = {\n meeting: {\n participants: { key: 'customers.schedule.attendees', fallback: 'Attendees' },\n linkedEntities: { key: 'customers.schedule.connections', fallback: 'Connections' },\n },\n call: {\n participants: { key: 'customers.schedule.contact', fallback: 'Contact' },\n linkedEntities: { key: 'customers.schedule.connections', fallback: 'Connections' },\n description: { key: 'customers.schedule.callNotes', fallback: 'Call notes' },\n },\n task: {\n date: { key: 'customers.schedule.dueDate', fallback: 'Due date' },\n startTime: { key: 'customers.schedule.dueTime', fallback: 'Due time' },\n duration: { key: 'customers.schedule.estimate', fallback: 'Estimate' },\n linkedEntities: { key: 'customers.schedule.connections', fallback: 'Connections' },\n description: { key: 'customers.schedule.details', fallback: 'Details' },\n },\n email: {\n title: { key: 'customers.schedule.subject', fallback: 'Subject' },\n participants: { key: 'customers.schedule.to', fallback: 'To' },\n linkedEntities: { key: 'customers.schedule.connections', fallback: 'Connections' },\n description: { key: 'customers.schedule.message', fallback: 'Message' },\n },\n}\n\nexport function isVisible(type: ActivityType, fieldId: ScheduleFieldId): boolean {\n return FIELD_VISIBILITY[type].has(fieldId)\n}\n\nexport function getFieldLabel(\n type: ActivityType,\n fieldId: ScheduleFieldId,\n t: (key: string, fallback: string) => string,\n defaultKey: string,\n defaultFallback: string,\n): string {\n const override = FIELD_LABEL_OVERRIDES[type]?.[fieldId]\n if (override) return t(override.key, override.fallback)\n return t(defaultKey, defaultFallback)\n}\n"],
|
|
5
|
+
"mappings": "AAkBO,MAAM,mBAA+D;AAAA,EAC1E,SAAS,oBAAI,IAAI;AAAA,IACf;AAAA,IAAS;AAAA,IAAQ;AAAA,IAAa;AAAA,IAAY;AAAA,IAAU;AAAA,IAAY;AAAA,IAChE;AAAA,IAAgB;AAAA,IAAoB;AAAA,IAAY;AAAA,IAAkB;AAAA,IAClE;AAAA,IAAY;AAAA,EACd,CAAC;AAAA,EACD,MAAM,oBAAI,IAAI;AAAA,IACZ;AAAA,IAAS;AAAA,IAAQ;AAAA,IAAa;AAAA,IAC9B;AAAA,IAAgB;AAAA,IAAkB;AAAA,IAClC;AAAA,IAAY;AAAA,EACd,CAAC;AAAA;AAAA,EAED,MAAM,oBAAI,IAAI;AAAA,IACZ;AAAA,IAAS;AAAA,IAAQ;AAAA,IAAa;AAAA,IAC9B;AAAA,IAAkB;AAAA,IAClB;AAAA,IAAY;AAAA,EACd,CAAC;AAAA;AAAA,EAED,OAAO,oBAAI,IAAI;AAAA,IACb;AAAA,IAAS;AAAA,IAAQ;AAAA,IACjB;AAAA,IAAgB;AAAA,IAAkB;AAAA,IAClC;AAAA,IAAY;AAAA,EACd,CAAC;AACH;AAOO,MAAM,wBAET;AAAA,EACF,SAAS;AAAA,IACP,cAAc,EAAE,KAAK,gCAAgC,UAAU,YAAY;AAAA,IAC3E,gBAAgB,EAAE,KAAK,kCAAkC,UAAU,cAAc;AAAA,EACnF;AAAA,EACA,MAAM;AAAA,IACJ,cAAc,EAAE,KAAK,8BAA8B,UAAU,UAAU;AAAA,IACvE,gBAAgB,EAAE,KAAK,kCAAkC,UAAU,cAAc;AAAA,IACjF,aAAa,EAAE,KAAK,gCAAgC,UAAU,aAAa;AAAA,EAC7E;AAAA,EACA,MAAM;AAAA,IACJ,MAAM,EAAE,KAAK,8BAA8B,UAAU,WAAW;AAAA,IAChE,WAAW,EAAE,KAAK,8BAA8B,UAAU,WAAW;AAAA,IACrE,UAAU,EAAE,KAAK,+BAA+B,UAAU,WAAW;AAAA,IACrE,gBAAgB,EAAE,KAAK,kCAAkC,UAAU,cAAc;AAAA,IACjF,aAAa,EAAE,KAAK,8BAA8B,UAAU,UAAU;AAAA,EACxE;AAAA,EACA,OAAO;AAAA,IACL,OAAO,EAAE,KAAK,8BAA8B,UAAU,UAAU;AAAA,IAChE,cAAc,EAAE,KAAK,yBAAyB,UAAU,KAAK;AAAA,IAC7D,gBAAgB,EAAE,KAAK,kCAAkC,UAAU,cAAc;AAAA,IACjF,aAAa,EAAE,KAAK,8BAA8B,UAAU,UAAU;AAAA,EACxE;AACF;AAEO,SAAS,UAAU,MAAoB,SAAmC;AAC/E,SAAO,iBAAiB,IAAI,EAAE,IAAI,OAAO;AAC3C;AAEO,SAAS,cACd,MACA,SACA,GACA,YACA,iBACQ;AACR,QAAM,WAAW,sBAAsB,IAAI,IAAI,OAAO;AACtD,MAAI,SAAU,QAAO,EAAE,SAAS,KAAK,SAAS,QAAQ;AACtD,SAAO,EAAE,YAAY,eAAe;AACtC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -7,6 +7,12 @@ const PARTICIPANT_COLORS = [
|
|
|
7
7
|
"bg-chart-pink",
|
|
8
8
|
"bg-chart-teal"
|
|
9
9
|
];
|
|
10
|
+
const DEFAULT_REMINDER_MINUTES = {
|
|
11
|
+
meeting: 15,
|
|
12
|
+
call: 5,
|
|
13
|
+
task: 1440,
|
|
14
|
+
email: 15
|
|
15
|
+
};
|
|
10
16
|
function useScheduleFormState({ open, editData }) {
|
|
11
17
|
const [activityType, setActivityType] = React.useState("meeting");
|
|
12
18
|
const [title, setTitle] = React.useState("");
|
|
@@ -32,7 +38,8 @@ function useScheduleFormState({ open, editData }) {
|
|
|
32
38
|
React.useEffect(() => {
|
|
33
39
|
if (open) {
|
|
34
40
|
if (editData) {
|
|
35
|
-
|
|
41
|
+
const resolvedType = editData.interactionType ?? "meeting";
|
|
42
|
+
setActivityType(resolvedType);
|
|
36
43
|
setTitle(editData.title ?? "");
|
|
37
44
|
const scheduledDate = editData.scheduledAt ? new Date(editData.scheduledAt) : /* @__PURE__ */ new Date();
|
|
38
45
|
setDate(scheduledDate.toISOString().slice(0, 10));
|
|
@@ -41,7 +48,7 @@ function useScheduleFormState({ open, editData }) {
|
|
|
41
48
|
setAllDay(editData.allDay ?? false);
|
|
42
49
|
setDescription(editData.body ?? "");
|
|
43
50
|
setLocation(editData.location ?? "");
|
|
44
|
-
setReminderMinutes(editData.reminderMinutes ??
|
|
51
|
+
setReminderMinutes(editData.reminderMinutes ?? DEFAULT_REMINDER_MINUTES[resolvedType]);
|
|
45
52
|
setVisibility(editData.visibility ?? "team");
|
|
46
53
|
setParticipants(
|
|
47
54
|
Array.isArray(editData.participants) ? editData.participants.map((p, i) => ({
|
|
@@ -95,7 +102,7 @@ function useScheduleFormState({ open, editData }) {
|
|
|
95
102
|
setAllDay(false);
|
|
96
103
|
setDescription("");
|
|
97
104
|
setLocation("");
|
|
98
|
-
setReminderMinutes(
|
|
105
|
+
setReminderMinutes(DEFAULT_REMINDER_MINUTES.meeting);
|
|
99
106
|
setVisibility("team");
|
|
100
107
|
setParticipants([]);
|
|
101
108
|
setLinkedEntities([]);
|
|
@@ -108,6 +115,16 @@ function useScheduleFormState({ open, editData }) {
|
|
|
108
115
|
document.body.style.removeProperty("pointer-events");
|
|
109
116
|
};
|
|
110
117
|
}, [open, editData]);
|
|
118
|
+
const lastReminderTypeRef = React.useRef("meeting");
|
|
119
|
+
React.useEffect(() => {
|
|
120
|
+
if (!open || editData) {
|
|
121
|
+
lastReminderTypeRef.current = activityType;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (lastReminderTypeRef.current === activityType) return;
|
|
125
|
+
lastReminderTypeRef.current = activityType;
|
|
126
|
+
setReminderMinutes(DEFAULT_REMINDER_MINUTES[activityType]);
|
|
127
|
+
}, [activityType, editData, open]);
|
|
111
128
|
const removeParticipant = React.useCallback((userId) => {
|
|
112
129
|
setParticipants((prev) => prev.filter((p) => p.userId !== userId));
|
|
113
130
|
}, []);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/components/detail/schedule/useScheduleFormState.ts"],
|
|
4
|
-
"sourcesContent": ["import * as React from 'react'\nimport type { ActivityType } from './fieldConfig'\n\nexport type RsvpStatus = 'pending' | 'accepted' | 'declined' | 'tentative'\n\nexport type Participant = {\n userId: string\n name: string\n email?: string\n color?: string\n status?: RsvpStatus\n}\n\nexport type LinkedEntity = {\n id: string\n type: 'company' | 'deal' | 'offer'\n label: string\n}\n\nexport type ScheduleActivityEditData = {\n id: string\n interactionType?: string\n title?: string | null\n body?: string | null\n scheduledAt?: string | null\n durationMinutes?: number | null\n location?: string | null\n allDay?: boolean | null\n recurrenceRule?: string | null\n recurrenceEnd?: string | null\n participants?: Array<{ userId: string; name?: string; email?: string; status?: string }> | null\n reminderMinutes?: number | null\n visibility?: string | null\n linkedEntities?: Array<{ id: string; type: string; label: string }> | null\n guestPermissions?: { canInviteOthers?: boolean; canModify?: boolean; canSeeList?: boolean } | null\n}\n\nexport const PARTICIPANT_COLORS = [\n 'bg-chart-emerald',\n 'bg-chart-blue',\n 'bg-chart-orange',\n 'bg-chart-violet',\n 'bg-chart-pink',\n 'bg-chart-teal',\n]\n\ninterface UseScheduleFormStateParams {\n open: boolean\n editData: ScheduleActivityEditData | null | undefined\n}\n\nexport function useScheduleFormState({ open, editData }: UseScheduleFormStateParams) {\n const [activityType, setActivityType] = React.useState<ActivityType>('meeting')\n const [title, setTitle] = React.useState('')\n const [date, setDate] = React.useState(() => new Date().toISOString().slice(0, 10))\n const [startTime, setStartTime] = React.useState('10:00')\n const [duration, setDuration] = React.useState(30)\n const [allDay, setAllDay] = React.useState(false)\n const [description, setDescription] = React.useState('')\n const [markdownEnabled, setMarkdownEnabled] = React.useState(true)\n const [location, setLocation] = React.useState('')\n const [reminderMinutes, setReminderMinutes] = React.useState(15)\n const [visibility, setVisibility] = React.useState('team')\n const [participants, setParticipants] = React.useState<Participant[]>([])\n const [linkedEntities, setLinkedEntities] = React.useState<LinkedEntity[]>([])\n const [recurrenceEnabled, setRecurrenceEnabled] = React.useState(false)\n const [recurrenceDays, setRecurrenceDays] = React.useState<boolean[]>([true, false, true, false, false, false, false])\n const [recurrenceEndType, setRecurrenceEndType] = React.useState<'never' | 'count' | 'date'>('never')\n const [recurrenceCount, setRecurrenceCount] = React.useState(8)\n const [recurrenceEndDate, setRecurrenceEndDate] = React.useState('')\n const [conflict, setConflict] = React.useState<string | null>(null)\n const [saving, setSaving] = React.useState(false)\n const [guestPermissions, setGuestPermissions] = React.useState({ canInviteOthers: true, canModify: false, canSeeList: true })\n\n React.useEffect(() => {\n if (open) {\n if (editData) {\n // Edit mode: populate from existing interaction\n
|
|
5
|
-
"mappings": "AAAA,YAAY,WAAW;AAqChB,MAAM,qBAAqB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOO,SAAS,qBAAqB,EAAE,MAAM,SAAS,GAA+B;AACnF,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAuB,SAAS;AAC9E,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,EAAE;AAC3C,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,OAAM,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAClF,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,OAAO;AACxD,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,EAAE;AACjD,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,KAAK;AAChD,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,EAAE;AACvD,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,IAAI;AACjE,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,EAAE;AACjD,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,EAAE;AAC/D,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,MAAM;AACzD,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAwB,CAAC,CAAC;AACxE,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAyB,CAAC,CAAC;AAC7E,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAS,KAAK;AACtE,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAoB,CAAC,MAAM,OAAO,MAAM,OAAO,OAAO,OAAO,KAAK,CAAC;AACrH,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAqC,OAAO;AACpG,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,CAAC;AAC9D,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAS,EAAE;AACnE,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAwB,IAAI;AAClE,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,KAAK;AAChD,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAS,EAAE,iBAAiB,MAAM,WAAW,OAAO,YAAY,KAAK,CAAC;AAE5H,QAAM,UAAU,MAAM;AACpB,QAAI,MAAM;AACR,UAAI,UAAU;AAEZ,
|
|
4
|
+
"sourcesContent": ["import * as React from 'react'\nimport type { ActivityType } from './fieldConfig'\n\nexport type RsvpStatus = 'pending' | 'accepted' | 'declined' | 'tentative'\n\nexport type Participant = {\n userId: string\n name: string\n email?: string\n color?: string\n status?: RsvpStatus\n}\n\nexport type LinkedEntity = {\n id: string\n type: 'company' | 'deal' | 'offer'\n label: string\n}\n\nexport type ScheduleActivityEditData = {\n id: string\n interactionType?: string\n title?: string | null\n body?: string | null\n scheduledAt?: string | null\n durationMinutes?: number | null\n location?: string | null\n allDay?: boolean | null\n recurrenceRule?: string | null\n recurrenceEnd?: string | null\n participants?: Array<{ userId: string; name?: string; email?: string; status?: string }> | null\n reminderMinutes?: number | null\n visibility?: string | null\n linkedEntities?: Array<{ id: string; type: string; label: string }> | null\n guestPermissions?: { canInviteOthers?: boolean; canModify?: boolean; canSeeList?: boolean } | null\n}\n\nexport const PARTICIPANT_COLORS = [\n 'bg-chart-emerald',\n 'bg-chart-blue',\n 'bg-chart-orange',\n 'bg-chart-violet',\n 'bg-chart-pink',\n 'bg-chart-teal',\n]\n\n// Per-Figma defaults for the Reminder dropdown when the user picks an activity\n// type. Meeting/email keep the standard 15 min; tasks default to 1 day (1440 min)\n// because they're plan-ahead artefacts; calls default to 5 min as a stand-in for\n// the Figma \"After call ends\" treatment (which would need a non-numeric sentinel\n// in the API contract \u2014 tracked as a follow-up).\nconst DEFAULT_REMINDER_MINUTES: Record<ActivityType, number> = {\n meeting: 15,\n call: 5,\n task: 1440,\n email: 15,\n}\n\ninterface UseScheduleFormStateParams {\n open: boolean\n editData: ScheduleActivityEditData | null | undefined\n}\n\nexport function useScheduleFormState({ open, editData }: UseScheduleFormStateParams) {\n const [activityType, setActivityType] = React.useState<ActivityType>('meeting')\n const [title, setTitle] = React.useState('')\n const [date, setDate] = React.useState(() => new Date().toISOString().slice(0, 10))\n const [startTime, setStartTime] = React.useState('10:00')\n const [duration, setDuration] = React.useState(30)\n const [allDay, setAllDay] = React.useState(false)\n const [description, setDescription] = React.useState('')\n const [markdownEnabled, setMarkdownEnabled] = React.useState(true)\n const [location, setLocation] = React.useState('')\n const [reminderMinutes, setReminderMinutes] = React.useState(15)\n const [visibility, setVisibility] = React.useState('team')\n const [participants, setParticipants] = React.useState<Participant[]>([])\n const [linkedEntities, setLinkedEntities] = React.useState<LinkedEntity[]>([])\n const [recurrenceEnabled, setRecurrenceEnabled] = React.useState(false)\n const [recurrenceDays, setRecurrenceDays] = React.useState<boolean[]>([true, false, true, false, false, false, false])\n const [recurrenceEndType, setRecurrenceEndType] = React.useState<'never' | 'count' | 'date'>('never')\n const [recurrenceCount, setRecurrenceCount] = React.useState(8)\n const [recurrenceEndDate, setRecurrenceEndDate] = React.useState('')\n const [conflict, setConflict] = React.useState<string | null>(null)\n const [saving, setSaving] = React.useState(false)\n const [guestPermissions, setGuestPermissions] = React.useState({ canInviteOthers: true, canModify: false, canSeeList: true })\n\n React.useEffect(() => {\n if (open) {\n if (editData) {\n // Edit mode: populate from existing interaction\n const resolvedType = (editData.interactionType as ActivityType) ?? 'meeting'\n setActivityType(resolvedType)\n setTitle(editData.title ?? '')\n const scheduledDate = editData.scheduledAt ? new Date(editData.scheduledAt) : new Date()\n setDate(scheduledDate.toISOString().slice(0, 10))\n setStartTime(scheduledDate.toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' }))\n setDuration(editData.durationMinutes ?? 30)\n setAllDay(editData.allDay ?? false)\n setDescription(editData.body ?? '')\n setLocation(editData.location ?? '')\n // Use per-type default when the editData omits an explicit reminder\n // (the menu-driven \"New X\" flow opens the dialog with `reminderMinutes: null`).\n setReminderMinutes(editData.reminderMinutes ?? DEFAULT_REMINDER_MINUTES[resolvedType])\n setVisibility(editData.visibility ?? 'team')\n setParticipants(\n Array.isArray(editData.participants)\n ? editData.participants.map((p, i) => ({\n userId: p.userId,\n name: p.name ?? p.userId,\n email: p.email,\n color: PARTICIPANT_COLORS[i % PARTICIPANT_COLORS.length],\n status: (p.status ?? 'pending') as RsvpStatus,\n }))\n : [],\n )\n setLinkedEntities(\n Array.isArray(editData.linkedEntities)\n ? editData.linkedEntities.map((e) => ({ id: e.id, type: e.type as LinkedEntity['type'], label: e.label }))\n : [],\n )\n if (editData.recurrenceRule) {\n setRecurrenceEnabled(true)\n // Parse RRULE to set days and end type\n const rule = editData.recurrenceRule\n const byDayMatch = rule.match(/BYDAY=([A-Z,]+)/)\n if (byDayMatch) {\n const dayNames = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU']\n const activeDays = byDayMatch[1].split(',')\n setRecurrenceDays(dayNames.map((d) => activeDays.includes(d)))\n }\n const countMatch = rule.match(/COUNT=(\\d+)/)\n const untilMatch = rule.match(/UNTIL=(\\d{8})/)\n if (countMatch) {\n setRecurrenceEndType('count')\n setRecurrenceCount(Number(countMatch[1]))\n } else if (untilMatch) {\n setRecurrenceEndType('date')\n const raw = untilMatch[1]\n setRecurrenceEndDate(`${raw.slice(0, 4)}-${raw.slice(4, 6)}-${raw.slice(6, 8)}`)\n } else {\n setRecurrenceEndType('never')\n }\n } else {\n setRecurrenceEnabled(false)\n }\n if (editData.guestPermissions) {\n setGuestPermissions({\n canInviteOthers: editData.guestPermissions.canInviteOthers ?? true,\n canModify: editData.guestPermissions.canModify ?? false,\n canSeeList: editData.guestPermissions.canSeeList ?? true,\n })\n }\n } else {\n // Create mode: reset all fields\n setActivityType('meeting')\n setTitle('')\n setDate(new Date().toISOString().slice(0, 10))\n setStartTime('10:00')\n setDuration(30)\n setAllDay(false)\n setDescription('')\n setLocation('')\n setReminderMinutes(DEFAULT_REMINDER_MINUTES.meeting)\n setVisibility('team')\n setParticipants([])\n setLinkedEntities([])\n setRecurrenceEnabled(false)\n }\n setConflict(null)\n }\n return () => {\n // Safety net: restore body scroll if Radix Dialog fails to clean up\n document.body.style.removeProperty('overflow')\n document.body.style.removeProperty('pointer-events')\n }\n }, [open, editData])\n\n // Update the Reminder default when the activity type changes in create mode.\n // Skipped in edit mode (the persisted value wins), and gated by `open` to\n // avoid flipping the default in a closed-but-mounted dialog.\n const lastReminderTypeRef = React.useRef<ActivityType>('meeting')\n React.useEffect(() => {\n if (!open || editData) {\n lastReminderTypeRef.current = activityType\n return\n }\n if (lastReminderTypeRef.current === activityType) return\n lastReminderTypeRef.current = activityType\n setReminderMinutes(DEFAULT_REMINDER_MINUTES[activityType])\n }, [activityType, editData, open])\n\n const removeParticipant = React.useCallback((userId: string) => {\n setParticipants((prev) => prev.filter((p) => p.userId !== userId))\n }, [])\n\n const toggleRecurrenceDay = React.useCallback((index: number) => {\n setRecurrenceDays((prev) => {\n const next = [...prev]\n next[index] = !next[index]\n return next\n })\n }, [])\n\n return {\n activityType,\n setActivityType,\n title,\n setTitle,\n date,\n setDate,\n startTime,\n setStartTime,\n duration,\n setDuration,\n allDay,\n setAllDay,\n description,\n setDescription,\n markdownEnabled,\n setMarkdownEnabled,\n location,\n setLocation,\n reminderMinutes,\n setReminderMinutes,\n visibility,\n setVisibility,\n participants,\n setParticipants,\n linkedEntities,\n setLinkedEntities,\n recurrenceEnabled,\n setRecurrenceEnabled,\n recurrenceDays,\n setRecurrenceDays,\n recurrenceEndType,\n setRecurrenceEndType,\n recurrenceCount,\n setRecurrenceCount,\n recurrenceEndDate,\n setRecurrenceEndDate,\n conflict,\n setConflict,\n saving,\n setSaving,\n guestPermissions,\n setGuestPermissions,\n removeParticipant,\n toggleRecurrenceDay,\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,YAAY,WAAW;AAqChB,MAAM,qBAAqB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAOA,MAAM,2BAAyD;AAAA,EAC7D,SAAS;AAAA,EACT,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AAOO,SAAS,qBAAqB,EAAE,MAAM,SAAS,GAA+B;AACnF,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAuB,SAAS;AAC9E,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAS,EAAE;AAC3C,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,OAAM,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAClF,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,OAAO;AACxD,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,EAAE;AACjD,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,KAAK;AAChD,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,EAAE;AACvD,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,IAAI;AACjE,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,EAAE;AACjD,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,EAAE;AAC/D,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,MAAM;AACzD,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAwB,CAAC,CAAC;AACxE,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAyB,CAAC,CAAC;AAC7E,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAS,KAAK;AACtE,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAoB,CAAC,MAAM,OAAO,MAAM,OAAO,OAAO,OAAO,KAAK,CAAC;AACrH,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAqC,OAAO;AACpG,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,CAAC;AAC9D,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAS,EAAE;AACnE,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAwB,IAAI;AAClE,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,KAAK;AAChD,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAS,EAAE,iBAAiB,MAAM,WAAW,OAAO,YAAY,KAAK,CAAC;AAE5H,QAAM,UAAU,MAAM;AACpB,QAAI,MAAM;AACR,UAAI,UAAU;AAEZ,cAAM,eAAgB,SAAS,mBAAoC;AACnE,wBAAgB,YAAY;AAC5B,iBAAS,SAAS,SAAS,EAAE;AAC7B,cAAM,gBAAgB,SAAS,cAAc,IAAI,KAAK,SAAS,WAAW,IAAI,oBAAI,KAAK;AACvF,gBAAQ,cAAc,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAChD,qBAAa,cAAc,mBAAmB,SAAS,EAAE,MAAM,WAAW,QAAQ,UAAU,CAAC,CAAC;AAC9F,oBAAY,SAAS,mBAAmB,EAAE;AAC1C,kBAAU,SAAS,UAAU,KAAK;AAClC,uBAAe,SAAS,QAAQ,EAAE;AAClC,oBAAY,SAAS,YAAY,EAAE;AAGnC,2BAAmB,SAAS,mBAAmB,yBAAyB,YAAY,CAAC;AACrF,sBAAc,SAAS,cAAc,MAAM;AAC3C;AAAA,UACE,MAAM,QAAQ,SAAS,YAAY,IAC/B,SAAS,aAAa,IAAI,CAAC,GAAG,OAAO;AAAA,YACnC,QAAQ,EAAE;AAAA,YACV,MAAM,EAAE,QAAQ,EAAE;AAAA,YAClB,OAAO,EAAE;AAAA,YACT,OAAO,mBAAmB,IAAI,mBAAmB,MAAM;AAAA,YACvD,QAAS,EAAE,UAAU;AAAA,UACvB,EAAE,IACF,CAAC;AAAA,QACP;AACA;AAAA,UACE,MAAM,QAAQ,SAAS,cAAc,IACjC,SAAS,eAAe,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,MAAM,EAAE,MAA8B,OAAO,EAAE,MAAM,EAAE,IACvG,CAAC;AAAA,QACP;AACA,YAAI,SAAS,gBAAgB;AAC3B,+BAAqB,IAAI;AAEzB,gBAAM,OAAO,SAAS;AACtB,gBAAM,aAAa,KAAK,MAAM,iBAAiB;AAC/C,cAAI,YAAY;AACd,kBAAM,WAAW,CAAC,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,IAAI;AAC1D,kBAAM,aAAa,WAAW,CAAC,EAAE,MAAM,GAAG;AAC1C,8BAAkB,SAAS,IAAI,CAAC,MAAM,WAAW,SAAS,CAAC,CAAC,CAAC;AAAA,UAC/D;AACA,gBAAM,aAAa,KAAK,MAAM,aAAa;AAC3C,gBAAM,aAAa,KAAK,MAAM,eAAe;AAC7C,cAAI,YAAY;AACd,iCAAqB,OAAO;AAC5B,+BAAmB,OAAO,WAAW,CAAC,CAAC,CAAC;AAAA,UAC1C,WAAW,YAAY;AACrB,iCAAqB,MAAM;AAC3B,kBAAM,MAAM,WAAW,CAAC;AACxB,iCAAqB,GAAG,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,MAAM,GAAG,CAAC,CAAC,IAAI,IAAI,MAAM,GAAG,CAAC,CAAC,EAAE;AAAA,UACjF,OAAO;AACL,iCAAqB,OAAO;AAAA,UAC9B;AAAA,QACF,OAAO;AACL,+BAAqB,KAAK;AAAA,QAC5B;AACA,YAAI,SAAS,kBAAkB;AAC7B,8BAAoB;AAAA,YAClB,iBAAiB,SAAS,iBAAiB,mBAAmB;AAAA,YAC9D,WAAW,SAAS,iBAAiB,aAAa;AAAA,YAClD,YAAY,SAAS,iBAAiB,cAAc;AAAA,UACtD,CAAC;AAAA,QACH;AAAA,MACF,OAAO;AAEL,wBAAgB,SAAS;AACzB,iBAAS,EAAE;AACX,iBAAQ,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE,CAAC;AAC7C,qBAAa,OAAO;AACpB,oBAAY,EAAE;AACd,kBAAU,KAAK;AACf,uBAAe,EAAE;AACjB,oBAAY,EAAE;AACd,2BAAmB,yBAAyB,OAAO;AACnD,sBAAc,MAAM;AACpB,wBAAgB,CAAC,CAAC;AAClB,0BAAkB,CAAC,CAAC;AACpB,6BAAqB,KAAK;AAAA,MAC5B;AACA,kBAAY,IAAI;AAAA,IAClB;AACA,WAAO,MAAM;AAEX,eAAS,KAAK,MAAM,eAAe,UAAU;AAC7C,eAAS,KAAK,MAAM,eAAe,gBAAgB;AAAA,IACrD;AAAA,EACF,GAAG,CAAC,MAAM,QAAQ,CAAC;AAKnB,QAAM,sBAAsB,MAAM,OAAqB,SAAS;AAChE,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,QAAQ,UAAU;AACrB,0BAAoB,UAAU;AAC9B;AAAA,IACF;AACA,QAAI,oBAAoB,YAAY,aAAc;AAClD,wBAAoB,UAAU;AAC9B,uBAAmB,yBAAyB,YAAY,CAAC;AAAA,EAC3D,GAAG,CAAC,cAAc,UAAU,IAAI,CAAC;AAEjC,QAAM,oBAAoB,MAAM,YAAY,CAAC,WAAmB;AAC9D,oBAAgB,CAAC,SAAS,KAAK,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,CAAC;AAAA,EACnE,GAAG,CAAC,CAAC;AAEL,QAAM,sBAAsB,MAAM,YAAY,CAAC,UAAkB;AAC/D,sBAAkB,CAAC,SAAS;AAC1B,YAAM,OAAO,CAAC,GAAG,IAAI;AACrB,WAAK,KAAK,IAAI,CAAC,KAAK,KAAK;AACzB,aAAO;AAAA,IACT,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAEL,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@open-mercato/core",
|
|
3
|
-
"version": "0.5.1-develop.
|
|
3
|
+
"version": "0.5.1-develop.3036.f02c281f23",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -237,10 +237,10 @@
|
|
|
237
237
|
"ts-pattern": "^5.0.0"
|
|
238
238
|
},
|
|
239
239
|
"peerDependencies": {
|
|
240
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
240
|
+
"@open-mercato/shared": "0.5.1-develop.3036.f02c281f23"
|
|
241
241
|
},
|
|
242
242
|
"devDependencies": {
|
|
243
|
-
"@open-mercato/shared": "0.5.1-develop.
|
|
243
|
+
"@open-mercato/shared": "0.5.1-develop.3036.f02c281f23",
|
|
244
244
|
"@testing-library/dom": "^10.4.1",
|
|
245
245
|
"@testing-library/jest-dom": "^6.9.1",
|
|
246
246
|
"@testing-library/react": "^16.3.1",
|
|
@@ -21,8 +21,8 @@ import { z } from 'zod'
|
|
|
21
21
|
|
|
22
22
|
export const metadata = {
|
|
23
23
|
GET: { requireAuth: true },
|
|
24
|
-
PUT: { requireAuth: true },
|
|
25
|
-
DELETE: { requireAuth: true },
|
|
24
|
+
PUT: { requireAuth: true, requireFeatures: ['auth.sidebar.manage'] },
|
|
25
|
+
DELETE: { requireAuth: true, requireFeatures: ['auth.sidebar.manage'] },
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const sidebarSettingsSchema = z.object({
|
|
@@ -19,8 +19,8 @@ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
|
19
19
|
|
|
20
20
|
export const metadata = {
|
|
21
21
|
GET: { requireAuth: true },
|
|
22
|
-
PUT: { requireAuth: true },
|
|
23
|
-
DELETE: { requireAuth: true },
|
|
22
|
+
PUT: { requireAuth: true, requireFeatures: ['auth.sidebar.manage'] },
|
|
23
|
+
DELETE: { requireAuth: true, requireFeatures: ['auth.sidebar.manage'] },
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
const variantResponseSchema = z.object({
|
|
@@ -18,7 +18,7 @@ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
|
18
18
|
|
|
19
19
|
export const metadata = {
|
|
20
20
|
GET: { requireAuth: true },
|
|
21
|
-
POST: { requireAuth: true },
|
|
21
|
+
POST: { requireAuth: true, requireFeatures: ['auth.sidebar.manage'] },
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const variantListResponseSchema = z.object({
|