@open-mercato/core 0.9.0 → 0.9.1-develop.7462.1.94c09f158f
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/generated/entities/customer_dictionary_entry/index.js +2 -0
- package/dist/generated/entities/customer_dictionary_entry/index.js.map +2 -2
- package/dist/generated/entities/customer_interaction/index.js +2 -0
- package/dist/generated/entities/customer_interaction/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/attachments/api/route.js +29 -3
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/attachments/lib/ocrLimits.js +49 -0
- package/dist/modules/attachments/lib/ocrLimits.js.map +7 -0
- package/dist/modules/attachments/lib/ocrQueue.js +76 -2
- package/dist/modules/attachments/lib/ocrQueue.js.map +2 -2
- package/dist/modules/attachments/lib/ocrService.js +4 -0
- package/dist/modules/attachments/lib/ocrService.js.map +2 -2
- package/dist/modules/attachments/lib/pdfProcessing.js +14 -2
- package/dist/modules/attachments/lib/pdfProcessing.js.map +2 -2
- package/dist/modules/attachments/lib/scoped-upload-service.js +4 -2
- package/dist/modules/attachments/lib/scoped-upload-service.js.map +2 -2
- package/dist/modules/attachments/lib/textExtraction.js +13 -1
- package/dist/modules/attachments/lib/textExtraction.js.map +2 -2
- package/dist/modules/catalog/commands/categories.js +1 -1
- package/dist/modules/catalog/commands/categories.js.map +2 -2
- package/dist/modules/catalog/commands/productDeleteChildren.js +389 -0
- package/dist/modules/catalog/commands/productDeleteChildren.js.map +7 -0
- package/dist/modules/catalog/commands/products.js +52 -17
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/search.js +3 -1
- package/dist/modules/catalog/search.js.map +2 -2
- package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js +2 -1
- package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +4 -6
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js +4 -6
- package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +47 -11
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +3 -3
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +10 -7
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +2 -2
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js +5 -1
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
- package/dist/modules/communication_channels/lib/inbound-message-origin.js +1 -1
- package/dist/modules/communication_channels/lib/inbound-message-origin.js.map +2 -2
- package/dist/modules/communication_channels/lib/oauth-result.js +12 -0
- package/dist/modules/communication_channels/lib/oauth-result.js.map +7 -0
- package/dist/modules/communication_channels/lib/oauth-state.js +4 -1
- package/dist/modules/communication_channels/lib/oauth-state.js.map +2 -2
- package/dist/modules/communication_channels/lib/use-connect-channel.js +10 -0
- package/dist/modules/communication_channels/lib/use-connect-channel.js.map +2 -2
- package/dist/modules/customers/api/activity-types/route.js +151 -0
- package/dist/modules/customers/api/activity-types/route.js.map +7 -0
- package/dist/modules/customers/api/deals/[id]/people/route.js +58 -5
- package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +13 -5
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/route.js +20 -5
- package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/route.js +3 -0
- package/dist/modules/customers/api/interactions/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/tasks/route.js +1 -0
- package/dist/modules/customers/api/interactions/tasks/route.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js +18 -0
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +0 -23
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +15 -17
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/calendar-event-types.js +548 -0
- package/dist/modules/customers/calendar-event-types.js.map +7 -0
- package/dist/modules/customers/commands/dictionaries.js +70 -10
- package/dist/modules/customers/commands/dictionaries.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +187 -22
- package/dist/modules/customers/commands/interactions.js.map +3 -3
- package/dist/modules/customers/components/ActivityTypeEditor.js +288 -0
- package/dist/modules/customers/components/ActivityTypeEditor.js.map +7 -0
- package/dist/modules/customers/components/DictionarySettings.js +3 -1
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js +391 -297
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +3 -3
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +9 -74
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
- package/dist/modules/customers/components/calendar/TimeGrid.js +20 -16
- package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/EventTypePanel.js +218 -0
- package/dist/modules/customers/components/calendar/editor/EventTypePanel.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/LocationField.js +7 -3
- package/dist/modules/customers/components/calendar/editor/LocationField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +5 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PriorityField.js +3 -1
- package/dist/modules/customers/components/calendar/editor/PriorityField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/RepeatField.js +9 -1
- package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ResourcesField.js +4 -1
- package/dist/modules/customers/components/calendar/editor/ResourcesField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +15 -7
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +3 -1
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/hooks.js +28 -13
- package/dist/modules/customers/components/calendar/editor/hooks.js.map +3 -3
- package/dist/modules/customers/components/calendar/editor/inputs.js +14 -6
- package/dist/modules/customers/components/calendar/editor/inputs.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js +54 -0
- package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js +82 -0
- package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js.map +7 -0
- package/dist/modules/customers/components/calendar/types.js +1 -0
- package/dist/modules/customers/components/calendar/types.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js +78 -343
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
- package/dist/modules/customers/components/detail/CreatePersonDialog.js +12 -11
- package/dist/modules/customers/components/detail/CreatePersonDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +7 -3
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/DealPeopleSection.js +206 -0
- package/dist/modules/customers/components/detail/DealPeopleSection.js.map +7 -0
- package/dist/modules/customers/components/detail/LinkedPeopleSection.js +342 -0
- package/dist/modules/customers/components/detail/LinkedPeopleSection.js.map +7 -0
- package/dist/modules/customers/components/detail/PersonCard.js +5 -2
- package/dist/modules/customers/components/detail/PersonCard.js.map +2 -2
- package/dist/modules/customers/components/linking/adapters/personAdapter.js +3 -0
- package/dist/modules/customers/components/linking/adapters/personAdapter.js.map +2 -2
- package/dist/modules/customers/data/entities.js +6 -0
- package/dist/modules/customers/data/entities.js.map +2 -2
- package/dist/modules/customers/data/validators.js +39 -10
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/di.js +20 -1
- package/dist/modules/customers/di.js.map +2 -2
- package/dist/modules/customers/extension-points.js +13 -0
- package/dist/modules/customers/extension-points.js.map +2 -2
- package/dist/modules/customers/lib/calendar/conflicts.js +59 -16
- package/dist/modules/customers/lib/calendar/conflicts.js.map +2 -2
- package/dist/modules/customers/lib/calendar/editorPayload.js +34 -16
- package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
- package/dist/modules/customers/lib/calendar/eventTypeResolver.js +197 -0
- package/dist/modules/customers/lib/calendar/eventTypeResolver.js.map +7 -0
- package/dist/modules/customers/lib/calendar/interactionApplicability.js +67 -0
- package/dist/modules/customers/lib/calendar/interactionApplicability.js.map +7 -0
- package/dist/modules/customers/lib/calendar/mapItem.js +5 -2
- package/dist/modules/customers/lib/calendar/mapItem.js.map +2 -2
- package/dist/modules/customers/lib/calendar/recurrence.js +62 -1
- package/dist/modules/customers/lib/calendar/recurrence.js.map +2 -2
- package/dist/modules/customers/lib/calendar/timezone.js +111 -0
- package/dist/modules/customers/lib/calendar/timezone.js.map +7 -0
- package/dist/modules/customers/lib/interactionReadModel.js +1 -0
- package/dist/modules/customers/lib/interactionReadModel.js.map +2 -2
- package/dist/modules/customers/lib/todoCompatibility.js +1 -0
- package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
- package/dist/modules/customers/migrations/Migration20260928175058_customers.js +17 -0
- package/dist/modules/customers/migrations/Migration20260928175058_customers.js.map +7 -0
- package/dist/modules/customers/migrations/Migration20260930110235_customers.js +17 -0
- package/dist/modules/customers/migrations/Migration20260930110235_customers.js.map +7 -0
- package/dist/modules/dictionaries/components/DictionaryTable.js +60 -51
- package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
- package/dist/modules/messages/subscribers/message-notification.js +13 -1
- package/dist/modules/messages/subscribers/message-notification.js.map +2 -2
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +201 -53
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
- package/dist/modules/payment_gateways/lib/gateway-service.js +37 -11
- package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
- package/dist/modules/planner/lib/availabilityMerge.js +101 -38
- package/dist/modules/planner/lib/availabilityMerge.js.map +3 -3
- package/dist/modules/planner/services/plannerAvailabilityService.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +159 -81
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/lineItemUtils.js +7 -0
- package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +12 -7
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
- package/dist/modules/sync_excel/lib/adapters/customers.js +28 -25
- package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
- package/generated/entities/customer_dictionary_entry/index.ts +1 -0
- package/generated/entities/customer_interaction/index.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +9 -8
- package/src/modules/attachments/api/route.ts +36 -3
- package/src/modules/attachments/lib/ocrLimits.ts +62 -0
- package/src/modules/attachments/lib/ocrQueue.ts +95 -2
- package/src/modules/attachments/lib/ocrService.ts +4 -0
- package/src/modules/attachments/lib/pdfProcessing.ts +14 -2
- package/src/modules/attachments/lib/scoped-upload-service.ts +4 -2
- package/src/modules/attachments/lib/textExtraction.ts +14 -1
- package/src/modules/auth/i18n/de.json +3 -0
- package/src/modules/auth/i18n/en.json +3 -0
- package/src/modules/auth/i18n/es.json +3 -0
- package/src/modules/auth/i18n/ko.json +3 -0
- package/src/modules/auth/i18n/pl.json +3 -0
- package/src/modules/catalog/commands/categories.ts +1 -1
- package/src/modules/catalog/commands/productDeleteChildren.ts +557 -0
- package/src/modules/catalog/commands/products.ts +58 -17
- package/src/modules/catalog/search.ts +5 -1
- package/src/modules/communication_channels/api/get/oauth/[provider]/callback/route.ts +2 -1
- package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +4 -6
- package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts +4 -6
- package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +46 -10
- package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +12 -9
- package/src/modules/communication_channels/commands/deliver-outbound-message.ts +6 -1
- package/src/modules/communication_channels/i18n/de.json +4 -1
- package/src/modules/communication_channels/i18n/en.json +4 -1
- package/src/modules/communication_channels/i18n/es.json +4 -1
- package/src/modules/communication_channels/i18n/ko.json +4 -1
- package/src/modules/communication_channels/i18n/pl.json +4 -1
- package/src/modules/communication_channels/lib/inbound-message-origin.ts +2 -6
- package/src/modules/communication_channels/lib/oauth-result.ts +13 -0
- package/src/modules/communication_channels/lib/oauth-state.ts +4 -1
- package/src/modules/communication_channels/lib/use-connect-channel.ts +10 -0
- package/src/modules/customers/api/activity-types/route.ts +154 -0
- package/src/modules/customers/api/deals/[id]/people/route.ts +82 -3
- package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -1
- package/src/modules/customers/api/dictionaries/[kind]/route.ts +18 -2
- package/src/modules/customers/api/interactions/route.ts +4 -0
- package/src/modules/customers/api/interactions/tasks/route.ts +1 -0
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.meta.ts +20 -0
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +0 -29
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +15 -17
- package/src/modules/customers/calendar-event-types.ts +674 -0
- package/src/modules/customers/commands/dictionaries.ts +84 -8
- package/src/modules/customers/commands/interactions.ts +244 -21
- package/src/modules/customers/components/ActivityTypeEditor.tsx +239 -0
- package/src/modules/customers/components/DictionarySettings.tsx +5 -3
- package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +347 -218
- package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +18 -96
- package/src/modules/customers/components/calendar/TimeGrid.tsx +20 -18
- package/src/modules/customers/components/calendar/editor/EventTypePanel.tsx +230 -0
- package/src/modules/customers/components/calendar/editor/LocationField.tsx +7 -1
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +4 -0
- package/src/modules/customers/components/calendar/editor/PriorityField.tsx +3 -0
- package/src/modules/customers/components/calendar/editor/RepeatField.tsx +10 -0
- package/src/modules/customers/components/calendar/editor/ResourcesField.tsx +4 -0
- package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +16 -4
- package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +3 -0
- package/src/modules/customers/components/calendar/editor/hooks.ts +29 -13
- package/src/modules/customers/components/calendar/editor/inputs.tsx +17 -5
- package/src/modules/customers/components/calendar/editor/useCalendarCustomFields.ts +49 -0
- package/src/modules/customers/components/calendar/editor/useEventTypeCatalog.ts +100 -0
- package/src/modules/customers/components/calendar/types.ts +1 -0
- package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +94 -388
- package/src/modules/customers/components/detail/CreatePersonDialog.tsx +39 -19
- package/src/modules/customers/components/detail/DealForm.tsx +7 -3
- package/src/modules/customers/components/detail/DealPeopleSection.tsx +256 -0
- package/src/modules/customers/components/detail/LinkedPeopleSection.tsx +474 -0
- package/src/modules/customers/components/detail/PersonCard.tsx +12 -4
- package/src/modules/customers/components/detail/types.ts +1 -0
- package/src/modules/customers/components/linking/adapters/personAdapter.tsx +4 -0
- package/src/modules/customers/data/entities.ts +8 -1
- package/src/modules/customers/data/validators.ts +42 -7
- package/src/modules/customers/di.ts +21 -0
- package/src/modules/customers/extension-points.ts +13 -0
- package/src/modules/customers/i18n/de.json +104 -8
- package/src/modules/customers/i18n/en.json +104 -8
- package/src/modules/customers/i18n/es.json +104 -8
- package/src/modules/customers/i18n/ko.json +104 -8
- package/src/modules/customers/i18n/pl.json +104 -8
- package/src/modules/customers/lib/calendar/conflicts.ts +106 -27
- package/src/modules/customers/lib/calendar/editorPayload.ts +39 -19
- package/src/modules/customers/lib/calendar/eventTypeResolver.ts +258 -0
- package/src/modules/customers/lib/calendar/interactionApplicability.ts +99 -0
- package/src/modules/customers/lib/calendar/mapItem.ts +6 -2
- package/src/modules/customers/lib/calendar/recurrence.ts +65 -1
- package/src/modules/customers/lib/calendar/timezone.ts +117 -0
- package/src/modules/customers/lib/interactionReadModel.ts +1 -0
- package/src/modules/customers/lib/todoCompatibility.ts +2 -0
- package/src/modules/customers/migrations/.snapshot-open-mercato.json +34 -0
- package/src/modules/customers/migrations/Migration20260928175058_customers.ts +15 -0
- package/src/modules/customers/migrations/Migration20260930110235_customers.ts +15 -0
- package/src/modules/dictionaries/components/DictionaryTable.tsx +63 -56
- package/src/modules/messages/subscribers/message-notification.ts +13 -3
- package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +243 -62
- package/src/modules/payment_gateways/lib/gateway-service.ts +39 -12
- package/src/modules/planner/lib/availabilityMerge.ts +106 -41
- package/src/modules/planner/services/plannerAvailabilityService.ts +4 -0
- package/src/modules/sales/components/documents/LineItemDialog.tsx +194 -111
- package/src/modules/sales/components/documents/lineItemUtils.ts +18 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +25 -13
- package/src/modules/sync_excel/lib/adapters/customers.ts +33 -27
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { E } from "../../../generated/entities.ids.generated.js";
|
|
5
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
6
|
+
import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
7
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
8
|
+
import { getTelemetryRuntime } from "@open-mercato/shared/lib/telemetry/runtime";
|
|
9
|
+
import { DataTable } from "@open-mercato/ui/backend/DataTable";
|
|
10
|
+
import { RowActions } from "@open-mercato/ui/backend/RowActions";
|
|
11
|
+
import { CrudForm } from "@open-mercato/ui/backend/CrudForm";
|
|
12
|
+
import { ErrorMessage, LoadingMessage } from "@open-mercato/ui/backend/detail";
|
|
13
|
+
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
14
|
+
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
15
|
+
import { surfaceRecordConflict } from "@open-mercato/ui/backend/conflicts";
|
|
16
|
+
import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
|
|
17
|
+
import { apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
|
|
18
|
+
import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
|
|
19
|
+
import { createCrudFormError } from "@open-mercato/ui/backend/utils/serverErrors";
|
|
20
|
+
import { Button } from "@open-mercato/ui/primitives/button";
|
|
21
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@open-mercato/ui/primitives/dialog";
|
|
22
|
+
import { calendarEventTypeBehaviorSchema, calendarEventTypeKeySchema, calendarEventTypes } from "../calendar-event-types.js";
|
|
23
|
+
import { getCustomerDictionarySettingsSectionId } from "../lib/dictionaries.js";
|
|
24
|
+
import { renderDictionaryColor, renderDictionaryIcon } from "@open-mercato/core/modules/dictionaries/components/dictionaryAppearance";
|
|
25
|
+
const logger = createLogger("customers");
|
|
26
|
+
const ENTITY_ID = E.customers.customer_interaction;
|
|
27
|
+
const API = "/api/customers/dictionaries/activity-types";
|
|
28
|
+
const defaults = calendarEventTypes[0].behavior;
|
|
29
|
+
function initialValues(item) {
|
|
30
|
+
const behavior = item?.behavior ?? defaults;
|
|
31
|
+
return {
|
|
32
|
+
value: item?.key ?? "",
|
|
33
|
+
label: item?.label ?? "",
|
|
34
|
+
icon: item?.icon ?? "",
|
|
35
|
+
color: item?.color ?? "",
|
|
36
|
+
order: behavior.order,
|
|
37
|
+
baseKind: behavior.baseKind,
|
|
38
|
+
selectable: behavior.selectable,
|
|
39
|
+
...behavior.fields,
|
|
40
|
+
customFieldsetIds: [...behavior.customFieldsetIds],
|
|
41
|
+
updatedAt: item?.updatedAt ?? void 0
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function ActivityTypeEditor({ title, description }) {
|
|
45
|
+
const t = useT();
|
|
46
|
+
const scopeVersion = useOrganizationScopeVersion();
|
|
47
|
+
const { confirm, ConfirmDialogElement } = useConfirmDialog();
|
|
48
|
+
const [items, setItems] = React.useState([]);
|
|
49
|
+
const [rows, setRows] = React.useState([]);
|
|
50
|
+
const [selected, setSelected] = React.useState(void 0);
|
|
51
|
+
const [fieldsets, setFieldsets] = React.useState([]);
|
|
52
|
+
const [fieldsetsStatus, setFieldsetsStatus] = React.useState("loading");
|
|
53
|
+
const [fieldsetAttempt, setFieldsetAttempt] = React.useState(0);
|
|
54
|
+
const [status, setStatus] = React.useState("loading");
|
|
55
|
+
const { runMutation, retryLastMutation } = useGuardedMutation({
|
|
56
|
+
contextId: "customers-activity-types",
|
|
57
|
+
blockedMessage: t("ui.forms.flash.saveBlocked", "Save blocked by validation")
|
|
58
|
+
});
|
|
59
|
+
const load = React.useCallback(async () => {
|
|
60
|
+
setStatus("loading");
|
|
61
|
+
try {
|
|
62
|
+
const [catalog, dictionary] = await Promise.all([
|
|
63
|
+
readApiResultOrThrow("/api/customers/activity-types"),
|
|
64
|
+
readApiResultOrThrow(API)
|
|
65
|
+
]);
|
|
66
|
+
if (!Array.isArray(catalog.items) || !Array.isArray(dictionary.items)) throw new Error("[internal] Invalid activity type response");
|
|
67
|
+
setItems(catalog.items);
|
|
68
|
+
setRows(dictionary.items);
|
|
69
|
+
setStatus("ready");
|
|
70
|
+
} catch (err) {
|
|
71
|
+
logger.error("customers.activity_types.settings.load failed", { err });
|
|
72
|
+
getTelemetryRuntime()?.reportError(err, {
|
|
73
|
+
module: "customers",
|
|
74
|
+
code: "customers.activity_type_settings_load_failed"
|
|
75
|
+
});
|
|
76
|
+
setStatus("error");
|
|
77
|
+
}
|
|
78
|
+
}, []);
|
|
79
|
+
React.useEffect(() => {
|
|
80
|
+
void load();
|
|
81
|
+
}, [load, scopeVersion]);
|
|
82
|
+
React.useEffect(() => {
|
|
83
|
+
if (selected === void 0) return;
|
|
84
|
+
let active = true;
|
|
85
|
+
setFieldsetsStatus("loading");
|
|
86
|
+
void readApiResultOrThrow(
|
|
87
|
+
`/api/entities/definitions?entityId=${encodeURIComponent(ENTITY_ID)}`
|
|
88
|
+
).then((result) => {
|
|
89
|
+
if (!active) return;
|
|
90
|
+
setFieldsets(result.fieldsetsByEntity?.[ENTITY_ID] ?? []);
|
|
91
|
+
setFieldsetsStatus("ready");
|
|
92
|
+
}).catch((err) => {
|
|
93
|
+
logger.error("customers.activity_types.fieldsets.load failed", { err });
|
|
94
|
+
getTelemetryRuntime()?.reportError(err, {
|
|
95
|
+
module: "customers",
|
|
96
|
+
code: "customers.activity_type_fieldsets_load_failed"
|
|
97
|
+
});
|
|
98
|
+
if (active) setFieldsetsStatus("error");
|
|
99
|
+
});
|
|
100
|
+
return () => {
|
|
101
|
+
active = false;
|
|
102
|
+
};
|
|
103
|
+
}, [selected, fieldsetAttempt]);
|
|
104
|
+
const localRow = (key) => rows.find((row) => row.value.toLowerCase() === key && !row.isInherited);
|
|
105
|
+
const remove = async (item) => {
|
|
106
|
+
const row = localRow(item.key);
|
|
107
|
+
if (!row) return;
|
|
108
|
+
const approved = await confirm({
|
|
109
|
+
title: t("customers.config.activityTypes.resetConfirm", "Remove the local activity type configuration?"),
|
|
110
|
+
variant: "destructive"
|
|
111
|
+
});
|
|
112
|
+
if (!approved) return;
|
|
113
|
+
try {
|
|
114
|
+
await runMutation({
|
|
115
|
+
operation: () => withScopedApiRequestHeaders(buildOptimisticLockHeader(row.updatedAt), () => apiCallOrThrow(`${API}/${encodeURIComponent(row.id)}`, { method: "DELETE" })),
|
|
116
|
+
context: { formId: "customers-activity-types", resourceKind: "customers.dictionary", retryLastMutation },
|
|
117
|
+
mutationPayload: { action: "delete", id: row.id, kind: "activity-types" }
|
|
118
|
+
});
|
|
119
|
+
flash(t("customers.config.activityTypes.removed", "Activity type configuration removed."), "success");
|
|
120
|
+
await load();
|
|
121
|
+
} catch (err) {
|
|
122
|
+
logger.error("customers.activity_types.settings.remove failed", { err });
|
|
123
|
+
getTelemetryRuntime()?.reportError(err, {
|
|
124
|
+
module: "customers",
|
|
125
|
+
code: "customers.activity_type_settings_remove_failed"
|
|
126
|
+
});
|
|
127
|
+
if (!surfaceRecordConflict(err, t, { onRefresh: () => {
|
|
128
|
+
void load();
|
|
129
|
+
} })) {
|
|
130
|
+
flash(t("customers.config.activityTypes.saveFailed", "Failed to save activity type."), "error");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const fields = React.useMemo(() => {
|
|
135
|
+
const label = (key, fallback) => t(`customers.config.activityTypes.fields.${key}`, fallback);
|
|
136
|
+
const options = (values) => values.map((value) => ({ value, label: t(`customers.config.activityTypes.options.${value}`, value) }));
|
|
137
|
+
const knownFieldsets = fieldsets.map(({ code, label: fieldsetLabel }) => ({ value: code, label: fieldsetLabel }));
|
|
138
|
+
const missing = selected?.behavior.customFieldsetIds.filter((code) => !fieldsets.some((fieldset) => fieldset.code === code)) ?? [];
|
|
139
|
+
return [
|
|
140
|
+
{ id: "value", type: "text", label: label("value", "Stable key"), required: true, readOnly: !!selected },
|
|
141
|
+
{ id: "label", type: "text", label: label("label", "Label"), required: true },
|
|
142
|
+
{ id: "icon", type: "text", label: label("icon", "Icon") },
|
|
143
|
+
{ id: "color", type: "text", label: label("color", "Color (#RRGGBB)") },
|
|
144
|
+
{ id: "order", type: "number", label: label("order", "Display order"), required: true },
|
|
145
|
+
{ id: "baseKind", type: "select", label: label("baseKind", "Base behavior"), options: options(["meeting", "call", "email", "note", "event", "task"]) },
|
|
146
|
+
{ id: "selectable", type: "checkbox", label: label("selectable", "Available for new events") },
|
|
147
|
+
...["endTime", "allDay", "recurrence", "priority", "resources"].map((id) => ({ id, type: "checkbox", label: label(id, id) })),
|
|
148
|
+
{ id: "location", type: "select", label: label("location", "Location field"), options: options(["none", "location", "phoneLink"]) },
|
|
149
|
+
{ id: "people", type: "select", label: label("people", "People field"), options: options(["none", "attendees", "participants", "recipients", "assignee"]) },
|
|
150
|
+
{
|
|
151
|
+
id: "customFieldsetIds",
|
|
152
|
+
type: "select",
|
|
153
|
+
multiple: true,
|
|
154
|
+
label: label("customFieldsetIds", "Custom-field fieldsets"),
|
|
155
|
+
options: [...knownFieldsets, ...missing.map((code) => ({ value: code, label: `${code} (${t("customers.config.activityTypes.missing", "missing")})` }))]
|
|
156
|
+
}
|
|
157
|
+
];
|
|
158
|
+
}, [fieldsets, selected, t]);
|
|
159
|
+
const groups = [
|
|
160
|
+
{ id: "appearance", title: t("customers.config.activityTypes.appearance", "Appearance"), fields: ["value", "label", "icon", "color", "order"] },
|
|
161
|
+
{ id: "behavior", title: t("customers.config.activityTypes.behavior", "Form behavior"), fields: ["baseKind", "selectable", "endTime", "allDay", "recurrence", "location", "people", "priority", "resources"] },
|
|
162
|
+
...fieldsets.length || selected?.behavior.customFieldsetIds.length ? [{ id: "fieldsets", fields: ["customFieldsetIds"] }] : []
|
|
163
|
+
];
|
|
164
|
+
const save = async (values) => {
|
|
165
|
+
const key = String(values.value ?? "").trim();
|
|
166
|
+
if (!calendarEventTypeKeySchema.safeParse(key).success) throw createCrudFormError(t("customers.config.activityTypes.invalidKey", "Use a lowercase key with letters, numbers, hyphens, or underscores."), { value: t("customers.config.activityTypes.invalidKey", "Use a lowercase key with letters, numbers, hyphens, or underscores.") });
|
|
167
|
+
if (fieldsetsStatus !== "ready") throw createCrudFormError(t("customers.config.activityTypes.fieldsetsUnavailable", "Fieldsets are unavailable. Retry loading before saving."));
|
|
168
|
+
const behavior = calendarEventTypeBehaviorSchema.safeParse({
|
|
169
|
+
schemaVersion: 1,
|
|
170
|
+
baseKind: values.baseKind,
|
|
171
|
+
selectable: values.selectable === true,
|
|
172
|
+
order: Number(values.order),
|
|
173
|
+
customFieldsetIds: values.customFieldsetIds ?? [],
|
|
174
|
+
fields: {
|
|
175
|
+
endTime: values.endTime === true,
|
|
176
|
+
allDay: values.allDay === true,
|
|
177
|
+
recurrence: values.recurrence === true,
|
|
178
|
+
location: values.location,
|
|
179
|
+
people: values.people,
|
|
180
|
+
priority: values.priority === true,
|
|
181
|
+
resources: values.resources === true
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
if (!behavior.success) throw createCrudFormError(t("customers.config.activityTypes.invalidBehavior", "Check the form behavior values."));
|
|
185
|
+
const payload = {
|
|
186
|
+
value: key,
|
|
187
|
+
label: String(values.label ?? "").trim(),
|
|
188
|
+
icon: String(values.icon ?? "").trim() || null,
|
|
189
|
+
color: String(values.color ?? "").trim() || null,
|
|
190
|
+
behavior: behavior.data
|
|
191
|
+
};
|
|
192
|
+
const row = selected ? localRow(selected.key) : void 0;
|
|
193
|
+
await apiCallOrThrow(row ? `${API}/${encodeURIComponent(row.id)}` : API, {
|
|
194
|
+
method: row ? "PATCH" : "POST",
|
|
195
|
+
headers: { "content-type": "application/json" },
|
|
196
|
+
body: JSON.stringify(row ? { label: payload.label, icon: payload.icon, color: payload.color, behavior: payload.behavior } : payload)
|
|
197
|
+
});
|
|
198
|
+
setSelected(void 0);
|
|
199
|
+
flash(t("customers.config.activityTypes.saved", "Activity type saved."), "success");
|
|
200
|
+
await load();
|
|
201
|
+
};
|
|
202
|
+
const columns = React.useMemo(() => [
|
|
203
|
+
{ accessorKey: "label", header: t("customers.config.activityTypes.columns.type", "Activity type"), cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
204
|
+
row.original.color ? renderDictionaryColor(row.original.color, "size-4 rounded-full border border-border") : null,
|
|
205
|
+
row.original.icon ? renderDictionaryIcon(row.original.icon, "size-4") : null,
|
|
206
|
+
/* @__PURE__ */ jsx("span", { children: row.original.labelKey ? t(row.original.labelKey, row.original.label) : row.original.label })
|
|
207
|
+
] }) },
|
|
208
|
+
{ accessorKey: "key", header: t("customers.config.activityTypes.columns.key", "Stable key") },
|
|
209
|
+
{ id: "source", header: t("customers.config.activityTypes.columns.source", "Source"), cell: ({ row }) => row.original.isLocalOverride ? t("customers.config.activityTypes.local", "Local override") : row.original.isInherited ? t("customers.config.activityTypes.inherited", "Inherited") : row.original.source },
|
|
210
|
+
{ id: "behavior", header: t("customers.config.activityTypes.columns.behavior", "Behavior"), cell: ({ row }) => t(`customers.config.activityTypes.options.${row.original.behavior.baseKind}`, row.original.behavior.baseKind) },
|
|
211
|
+
{ id: "active", header: t("customers.config.activityTypes.columns.active", "Selectable"), cell: ({ row }) => row.original.selectable ? t("customers.config.activityTypes.yes", "Yes") : t("customers.config.activityTypes.no", "No") },
|
|
212
|
+
{ id: "fields", header: t("customers.config.activityTypes.columns.fields", "Fields"), cell: ({ row }) => Object.values(row.original.behavior.fields).filter((value) => value !== false && value !== "none").length },
|
|
213
|
+
{ id: "fieldsets", header: t("customers.config.activityTypes.columns.fieldsets", "Fieldsets"), cell: ({ row }) => row.original.behavior.customFieldsetIds.length },
|
|
214
|
+
{ id: "order", header: t("customers.config.activityTypes.columns.order", "Order"), cell: ({ row }) => row.original.behavior.order }
|
|
215
|
+
], [t]);
|
|
216
|
+
return /* @__PURE__ */ jsxs("section", { id: getCustomerDictionarySettingsSectionId("activity-types"), className: "scroll-mt-24 rounded border bg-card text-card-foreground shadow-sm", children: [
|
|
217
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1 border-b px-6 py-4", children: [
|
|
218
|
+
/* @__PURE__ */ jsx("h2", { className: "text-lg font-medium", children: title }),
|
|
219
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: description })
|
|
220
|
+
] }),
|
|
221
|
+
/* @__PURE__ */ jsx("div", { className: "px-2 py-4 sm:px-4", children: status === "error" ? /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
222
|
+
/* @__PURE__ */ jsx(ErrorMessage, { label: t("customers.config.activityTypes.loadFailed", "Failed to load activity types.") }),
|
|
223
|
+
/* @__PURE__ */ jsx(Button, { type: "button", onClick: () => {
|
|
224
|
+
void load();
|
|
225
|
+
}, children: t("customers.config.activityTypes.retry", "Retry") })
|
|
226
|
+
] }) : /* @__PURE__ */ jsx(
|
|
227
|
+
DataTable,
|
|
228
|
+
{
|
|
229
|
+
embedded: true,
|
|
230
|
+
columns,
|
|
231
|
+
data: items,
|
|
232
|
+
isLoading: status === "loading",
|
|
233
|
+
title,
|
|
234
|
+
titleHeadingLevel: 2,
|
|
235
|
+
emptyState: t("customers.config.activityTypes.empty", "No activity types available."),
|
|
236
|
+
actions: /* @__PURE__ */ jsx(Button, { type: "button", size: "sm", onClick: () => setSelected(null), children: t("customers.config.activityTypes.new", "New activity type") }),
|
|
237
|
+
refreshButton: { label: t("customers.config.activityTypes.refresh", "Refresh"), onRefresh: load, isRefreshing: status === "loading" },
|
|
238
|
+
rowActions: (item) => {
|
|
239
|
+
const actions = [{ id: "edit", label: item.adminConfigurable ? t("customers.config.activityTypes.configure", "Configure") : t("customers.config.activityTypes.view", "View"), onSelect: () => setSelected(item) }];
|
|
240
|
+
if (localRow(item.key)) actions.push({
|
|
241
|
+
id: "delete",
|
|
242
|
+
label: item.source === "dictionary" ? t("customers.config.activityTypes.delete", "Delete") : t("customers.config.activityTypes.reset", "Reset override"),
|
|
243
|
+
destructive: true,
|
|
244
|
+
onSelect: () => {
|
|
245
|
+
void remove(item);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
return /* @__PURE__ */ jsx(RowActions, { items: actions });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
) }),
|
|
252
|
+
/* @__PURE__ */ jsx(Dialog, { open: selected !== void 0, onOpenChange: (open) => {
|
|
253
|
+
if (!open) setSelected(void 0);
|
|
254
|
+
}, children: /* @__PURE__ */ jsxs(DialogContent, { className: "max-h-screen overflow-y-auto sm:max-w-2xl", children: [
|
|
255
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: selected ? t("customers.config.activityTypes.configure", "Configure") : t("customers.config.activityTypes.new", "New activity type") }) }),
|
|
256
|
+
selected?.isInherited ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("customers.config.activityTypes.inheritedHint", "Saving creates a local override for this organization.") }) : null,
|
|
257
|
+
selected?.inactiveDictionaryOverride ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("customers.config.activityTypes.inactiveHint", "A module currently prevents this override from taking effect.") }) : null,
|
|
258
|
+
selected?.missingCustomFieldsetIds.length ? /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
|
|
259
|
+
t("customers.config.activityTypes.missingHint", "Some configured fieldsets no longer exist."),
|
|
260
|
+
": ",
|
|
261
|
+
selected.missingCustomFieldsetIds.join(", ")
|
|
262
|
+
] }) : null,
|
|
263
|
+
fieldsetsStatus === "loading" ? /* @__PURE__ */ jsx(LoadingMessage, { label: t("customers.config.activityTypes.loadingFieldsets", "Loading fieldsets\u2026") }) : null,
|
|
264
|
+
fieldsetsStatus === "error" ? /* @__PURE__ */ jsx(ErrorMessage, { label: t("customers.config.activityTypes.fieldsetsUnavailable", "Fieldsets are unavailable. Retry loading before saving.") }) : null,
|
|
265
|
+
fieldsetsStatus === "error" ? /* @__PURE__ */ jsx(Button, { type: "button", onClick: () => setFieldsetAttempt((attempt) => attempt + 1), children: t("customers.config.activityTypes.retry", "Retry") }) : null,
|
|
266
|
+
fieldsetsStatus === "ready" && fieldsets.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("customers.config.activityTypes.noFieldsets", "No custom-field fieldsets are defined yet.") }) : null,
|
|
267
|
+
selected !== void 0 && fieldsetsStatus === "ready" ? /* @__PURE__ */ jsx(
|
|
268
|
+
CrudForm,
|
|
269
|
+
{
|
|
270
|
+
embedded: true,
|
|
271
|
+
readOnly: selected?.adminConfigurable === false,
|
|
272
|
+
fields,
|
|
273
|
+
groups,
|
|
274
|
+
initialValues: initialValues(selected),
|
|
275
|
+
onSubmit: save,
|
|
276
|
+
submitLabel: t("customers.config.activityTypes.save", "Save"),
|
|
277
|
+
extraActions: /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => setSelected(void 0), children: t("customers.config.dictionaries.dialog.cancel", "Cancel") })
|
|
278
|
+
},
|
|
279
|
+
selected?.key ?? "new"
|
|
280
|
+
) : null
|
|
281
|
+
] }) }),
|
|
282
|
+
ConfirmDialogElement
|
|
283
|
+
] });
|
|
284
|
+
}
|
|
285
|
+
export {
|
|
286
|
+
ActivityTypeEditor
|
|
287
|
+
};
|
|
288
|
+
//# sourceMappingURL=ActivityTypeEditor.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/customers/components/ActivityTypeEditor.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { E } from '#generated/entities.ids.generated'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions, type RowActionItem } from '@open-mercato/ui/backend/RowActions'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { calendarEventTypeBehaviorSchema, calendarEventTypeKeySchema, calendarEventTypes } from '../calendar-event-types'\nimport type { ScopedCalendarEventType } from '../lib/calendar/eventTypeResolver'\nimport { getCustomerDictionarySettingsSectionId } from '../lib/dictionaries'\nimport { renderDictionaryColor, renderDictionaryIcon } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\n\nconst logger = createLogger('customers')\nconst ENTITY_ID = E.customers.customer_interaction\nconst API = '/api/customers/dictionaries/activity-types'\n\ntype DictionaryRow = { id: string; value: string; updatedAt: string | null; isInherited: boolean }\ntype Fieldset = { code: string; label: string }\ntype FormValues = Record<string, unknown>\n\nconst defaults = calendarEventTypes[0]!.behavior\n\nfunction initialValues(item: ScopedCalendarEventType | null): FormValues {\n const behavior = item?.behavior ?? defaults\n return {\n value: item?.key ?? '', label: item?.label ?? '', icon: item?.icon ?? '', color: item?.color ?? '',\n order: behavior.order, baseKind: behavior.baseKind, selectable: behavior.selectable,\n ...behavior.fields, customFieldsetIds: [...behavior.customFieldsetIds], updatedAt: item?.updatedAt ?? undefined,\n }\n}\n\nexport function ActivityTypeEditor({ title, description }: { title: string; description: string }) {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [items, setItems] = React.useState<ScopedCalendarEventType[]>([])\n const [rows, setRows] = React.useState<DictionaryRow[]>([])\n const [selected, setSelected] = React.useState<ScopedCalendarEventType | null | undefined>(undefined)\n const [fieldsets, setFieldsets] = React.useState<Fieldset[]>([])\n const [fieldsetsStatus, setFieldsetsStatus] = React.useState<'loading' | 'ready' | 'error'>('loading')\n const [fieldsetAttempt, setFieldsetAttempt] = React.useState(0)\n const [status, setStatus] = React.useState<'loading' | 'ready' | 'error'>('loading')\n const { runMutation, retryLastMutation } = useGuardedMutation({\n contextId: 'customers-activity-types',\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const load = React.useCallback(async () => {\n setStatus('loading')\n try {\n const [catalog, dictionary] = await Promise.all([\n readApiResultOrThrow<{ items: ScopedCalendarEventType[] }>('/api/customers/activity-types'),\n readApiResultOrThrow<{ items: DictionaryRow[] }>(API),\n ])\n if (!Array.isArray(catalog.items) || !Array.isArray(dictionary.items)) throw new Error('[internal] Invalid activity type response')\n setItems(catalog.items)\n setRows(dictionary.items)\n setStatus('ready')\n } catch (err) {\n logger.error('customers.activity_types.settings.load failed', { err })\n getTelemetryRuntime()?.reportError(err, {\n module: 'customers',\n code: 'customers.activity_type_settings_load_failed',\n })\n setStatus('error')\n }\n }, [])\n React.useEffect(() => { void load() }, [load, scopeVersion])\n\n React.useEffect(() => {\n if (selected === undefined) return\n let active = true\n setFieldsetsStatus('loading')\n void readApiResultOrThrow<{ fieldsetsByEntity?: Record<string, Fieldset[]> }>(\n `/api/entities/definitions?entityId=${encodeURIComponent(ENTITY_ID)}`,\n ).then((result) => {\n if (!active) return\n setFieldsets(result.fieldsetsByEntity?.[ENTITY_ID] ?? [])\n setFieldsetsStatus('ready')\n }).catch((err) => {\n logger.error('customers.activity_types.fieldsets.load failed', { err })\n getTelemetryRuntime()?.reportError(err, {\n module: 'customers',\n code: 'customers.activity_type_fieldsets_load_failed',\n })\n if (active) setFieldsetsStatus('error')\n })\n return () => { active = false }\n }, [selected, fieldsetAttempt])\n\n const localRow = (key: string) => rows.find((row) => row.value.toLowerCase() === key && !row.isInherited)\n const remove = async (item: ScopedCalendarEventType) => {\n const row = localRow(item.key)\n if (!row) return\n const approved = await confirm({\n title: t('customers.config.activityTypes.resetConfirm', 'Remove the local activity type configuration?'),\n variant: 'destructive',\n })\n if (!approved) return\n try {\n await runMutation({\n operation: () => withScopedApiRequestHeaders(buildOptimisticLockHeader(row.updatedAt), () =>\n apiCallOrThrow(`${API}/${encodeURIComponent(row.id)}`, { method: 'DELETE' })),\n context: { formId: 'customers-activity-types', resourceKind: 'customers.dictionary', retryLastMutation },\n mutationPayload: { action: 'delete', id: row.id, kind: 'activity-types' },\n })\n flash(t('customers.config.activityTypes.removed', 'Activity type configuration removed.'), 'success')\n await load()\n } catch (err) {\n logger.error('customers.activity_types.settings.remove failed', { err })\n getTelemetryRuntime()?.reportError(err, {\n module: 'customers',\n code: 'customers.activity_type_settings_remove_failed',\n })\n if (!surfaceRecordConflict(err, t, { onRefresh: () => { void load() } })) {\n flash(t('customers.config.activityTypes.saveFailed', 'Failed to save activity type.'), 'error')\n }\n }\n }\n\n const fields = React.useMemo<CrudField[]>(() => {\n const label = (key: string, fallback: string) => t(`customers.config.activityTypes.fields.${key}`, fallback)\n const options = (values: readonly string[]) => values.map((value) => ({ value, label: t(`customers.config.activityTypes.options.${value}`, value) }))\n const knownFieldsets = fieldsets.map(({ code, label: fieldsetLabel }) => ({ value: code, label: fieldsetLabel }))\n const missing = selected?.behavior.customFieldsetIds.filter((code) => !fieldsets.some((fieldset) => fieldset.code === code)) ?? []\n return [\n { id: 'value', type: 'text', label: label('value', 'Stable key'), required: true, readOnly: !!selected },\n { id: 'label', type: 'text', label: label('label', 'Label'), required: true },\n { id: 'icon', type: 'text', label: label('icon', 'Icon') },\n { id: 'color', type: 'text', label: label('color', 'Color (#RRGGBB)') },\n { id: 'order', type: 'number', label: label('order', 'Display order'), required: true },\n { id: 'baseKind', type: 'select', label: label('baseKind', 'Base behavior'), options: options(['meeting', 'call', 'email', 'note', 'event', 'task']) },\n { id: 'selectable', type: 'checkbox', label: label('selectable', 'Available for new events') },\n ...(['endTime', 'allDay', 'recurrence', 'priority', 'resources'] as const).map((id): CrudField => ({ id, type: 'checkbox', label: label(id, id) })),\n { id: 'location', type: 'select', label: label('location', 'Location field'), options: options(['none', 'location', 'phoneLink']) },\n { id: 'people', type: 'select', label: label('people', 'People field'), options: options(['none', 'attendees', 'participants', 'recipients', 'assignee']) },\n { id: 'customFieldsetIds', type: 'select', multiple: true, label: label('customFieldsetIds', 'Custom-field fieldsets'),\n options: [...knownFieldsets, ...missing.map((code) => ({ value: code, label: `${code} (${t('customers.config.activityTypes.missing', 'missing')})` }))] },\n ]\n }, [fieldsets, selected, t])\n const groups: CrudFormGroup[] = [\n { id: 'appearance', title: t('customers.config.activityTypes.appearance', 'Appearance'), fields: ['value', 'label', 'icon', 'color', 'order'] },\n { id: 'behavior', title: t('customers.config.activityTypes.behavior', 'Form behavior'), fields: ['baseKind', 'selectable', 'endTime', 'allDay', 'recurrence', 'location', 'people', 'priority', 'resources'] },\n ...(fieldsets.length || selected?.behavior.customFieldsetIds.length ? [{ id: 'fieldsets', fields: ['customFieldsetIds'] }] : []),\n ]\n const save = async (values: FormValues) => {\n const key = String(values.value ?? '').trim()\n if (!calendarEventTypeKeySchema.safeParse(key).success) throw createCrudFormError(t('customers.config.activityTypes.invalidKey', 'Use a lowercase key with letters, numbers, hyphens, or underscores.'), { value: t('customers.config.activityTypes.invalidKey', 'Use a lowercase key with letters, numbers, hyphens, or underscores.') })\n if (fieldsetsStatus !== 'ready') throw createCrudFormError(t('customers.config.activityTypes.fieldsetsUnavailable', 'Fieldsets are unavailable. Retry loading before saving.'))\n const behavior = calendarEventTypeBehaviorSchema.safeParse({\n schemaVersion: 1, baseKind: values.baseKind, selectable: values.selectable === true,\n order: Number(values.order), customFieldsetIds: values.customFieldsetIds ?? [],\n fields: { endTime: values.endTime === true, allDay: values.allDay === true, recurrence: values.recurrence === true,\n location: values.location, people: values.people, priority: values.priority === true, resources: values.resources === true },\n })\n if (!behavior.success) throw createCrudFormError(t('customers.config.activityTypes.invalidBehavior', 'Check the form behavior values.'))\n const payload = { value: key, label: String(values.label ?? '').trim(), icon: String(values.icon ?? '').trim() || null,\n color: String(values.color ?? '').trim() || null, behavior: behavior.data }\n const row = selected ? localRow(selected.key) : undefined\n await apiCallOrThrow(row ? `${API}/${encodeURIComponent(row.id)}` : API, {\n method: row ? 'PATCH' : 'POST', headers: { 'content-type': 'application/json' },\n body: JSON.stringify(row ? { label: payload.label, icon: payload.icon, color: payload.color, behavior: payload.behavior } : payload),\n })\n setSelected(undefined)\n flash(t('customers.config.activityTypes.saved', 'Activity type saved.'), 'success')\n await load()\n }\n\n const columns = React.useMemo<ColumnDef<ScopedCalendarEventType>[]>(() => [\n { accessorKey: 'label', header: t('customers.config.activityTypes.columns.type', 'Activity type'), cell: ({ row }) => (\n <div className=\"flex items-center gap-2\">{row.original.color ? renderDictionaryColor(row.original.color, 'size-4 rounded-full border border-border') : null}\n {row.original.icon ? renderDictionaryIcon(row.original.icon, 'size-4') : null}<span>{row.original.labelKey\n ? t(row.original.labelKey, row.original.label)\n : row.original.label}</span></div>) },\n { accessorKey: 'key', header: t('customers.config.activityTypes.columns.key', 'Stable key') },\n { id: 'source', header: t('customers.config.activityTypes.columns.source', 'Source'), cell: ({ row }) => row.original.isLocalOverride\n ? t('customers.config.activityTypes.local', 'Local override') : row.original.isInherited\n ? t('customers.config.activityTypes.inherited', 'Inherited') : row.original.source },\n { id: 'behavior', header: t('customers.config.activityTypes.columns.behavior', 'Behavior'), cell: ({ row }) =>\n t(`customers.config.activityTypes.options.${row.original.behavior.baseKind}`, row.original.behavior.baseKind) },\n { id: 'active', header: t('customers.config.activityTypes.columns.active', 'Selectable'), cell: ({ row }) => row.original.selectable\n ? t('customers.config.activityTypes.yes', 'Yes') : t('customers.config.activityTypes.no', 'No') },\n { id: 'fields', header: t('customers.config.activityTypes.columns.fields', 'Fields'), cell: ({ row }) =>\n Object.values(row.original.behavior.fields).filter((value) => value !== false && value !== 'none').length },\n { id: 'fieldsets', header: t('customers.config.activityTypes.columns.fieldsets', 'Fieldsets'), cell: ({ row }) => row.original.behavior.customFieldsetIds.length },\n { id: 'order', header: t('customers.config.activityTypes.columns.order', 'Order'), cell: ({ row }) => row.original.behavior.order },\n ], [t])\n\n return <section id={getCustomerDictionarySettingsSectionId('activity-types')} className=\"scroll-mt-24 rounded border bg-card text-card-foreground shadow-sm\">\n <div className=\"space-y-1 border-b px-6 py-4\"><h2 className=\"text-lg font-medium\">{title}</h2><p className=\"text-sm text-muted-foreground\">{description}</p></div>\n <div className=\"px-2 py-4 sm:px-4\">\n {status === 'error' ? <div className=\"space-y-3\"><ErrorMessage label={t('customers.config.activityTypes.loadFailed', 'Failed to load activity types.')} /><Button type=\"button\" onClick={() => { void load() }}>{t('customers.config.activityTypes.retry', 'Retry')}</Button></div> :\n <DataTable<ScopedCalendarEventType> embedded columns={columns} data={items} isLoading={status === 'loading'}\n title={title} titleHeadingLevel={2} emptyState={t('customers.config.activityTypes.empty', 'No activity types available.')}\n actions={<Button type=\"button\" size=\"sm\" onClick={() => setSelected(null)}>{t('customers.config.activityTypes.new', 'New activity type')}</Button>}\n refreshButton={{ label: t('customers.config.activityTypes.refresh', 'Refresh'), onRefresh: load, isRefreshing: status === 'loading' }}\n rowActions={(item) => {\n const actions: RowActionItem[] = [{ id: 'edit', label: item.adminConfigurable\n ? t('customers.config.activityTypes.configure', 'Configure') : t('customers.config.activityTypes.view', 'View'), onSelect: () => setSelected(item) }]\n if (localRow(item.key)) actions.push({ id: 'delete', label: item.source === 'dictionary'\n ? t('customers.config.activityTypes.delete', 'Delete') : t('customers.config.activityTypes.reset', 'Reset override'),\n destructive: true, onSelect: () => { void remove(item) } })\n return <RowActions items={actions} />\n }} />}\n </div>\n <Dialog open={selected !== undefined} onOpenChange={(open) => { if (!open) setSelected(undefined) }}>\n <DialogContent className=\"max-h-screen overflow-y-auto sm:max-w-2xl\">\n <DialogHeader><DialogTitle>{selected\n ? t('customers.config.activityTypes.configure', 'Configure') : t('customers.config.activityTypes.new', 'New activity type')}</DialogTitle></DialogHeader>\n {selected?.isInherited ? <p className=\"text-sm text-muted-foreground\">{t('customers.config.activityTypes.inheritedHint', 'Saving creates a local override for this organization.')}</p> : null}\n {selected?.inactiveDictionaryOverride ? <p className=\"text-sm text-muted-foreground\">{t('customers.config.activityTypes.inactiveHint', 'A module currently prevents this override from taking effect.')}</p> : null}\n {selected?.missingCustomFieldsetIds.length ? <p className=\"text-sm text-muted-foreground\">{t('customers.config.activityTypes.missingHint', 'Some configured fieldsets no longer exist.')}: {selected.missingCustomFieldsetIds.join(', ')}</p> : null}\n {fieldsetsStatus === 'loading' ? <LoadingMessage label={t('customers.config.activityTypes.loadingFieldsets', 'Loading fieldsets\u2026')} /> : null}\n {fieldsetsStatus === 'error' ? <ErrorMessage label={t('customers.config.activityTypes.fieldsetsUnavailable', 'Fieldsets are unavailable. Retry loading before saving.')} /> : null}\n {fieldsetsStatus === 'error' ? <Button type=\"button\" onClick={() => setFieldsetAttempt((attempt) => attempt + 1)}>{t('customers.config.activityTypes.retry', 'Retry')}</Button> : null}\n {fieldsetsStatus === 'ready' && fieldsets.length === 0 ? <p className=\"text-sm text-muted-foreground\">{t('customers.config.activityTypes.noFieldsets', 'No custom-field fieldsets are defined yet.')}</p> : null}\n {selected !== undefined && fieldsetsStatus === 'ready' ? <CrudForm<FormValues> key={selected?.key ?? 'new'} embedded\n readOnly={selected?.adminConfigurable === false} fields={fields} groups={groups}\n initialValues={initialValues(selected)} onSubmit={save} submitLabel={t('customers.config.activityTypes.save', 'Save')}\n extraActions={<Button type=\"button\" variant=\"outline\" onClick={() => setSelected(undefined)}>{t('customers.config.dictionaries.dialog.cancel', 'Cancel')}</Button>} /> : null}\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </section>\n}\n"],
|
|
5
|
+
"mappings": ";AAwLM,SACgF,KADhF;AAtLN,YAAY,WAAW;AAEvB,SAAS,SAAS;AAClB,SAAS,YAAY;AACrB,SAAS,mCAAmC;AAC5C,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AACpC,SAAS,iBAAiB;AAC1B,SAAS,kBAAsC;AAC/C,SAAS,gBAAoD;AAC7D,SAAS,cAAc,sBAAsB;AAC7C,SAAS,aAAa;AACtB,SAAS,wBAAwB;AACjC,SAAS,6BAA6B;AACtC,SAAS,0BAA0B;AACnC,SAAS,gBAAgB,sBAAsB,mCAAmC;AAClF,SAAS,iCAAiC;AAC1C,SAAS,2BAA2B;AACpC,SAAS,cAAc;AACvB,SAAS,QAAQ,eAAe,cAAc,mBAAmB;AACjE,SAAS,iCAAiC,4BAA4B,0BAA0B;AAEhG,SAAS,8CAA8C;AACvD,SAAS,uBAAuB,4BAA4B;AAE5D,MAAM,SAAS,aAAa,WAAW;AACvC,MAAM,YAAY,EAAE,UAAU;AAC9B,MAAM,MAAM;AAMZ,MAAM,WAAW,mBAAmB,CAAC,EAAG;AAExC,SAAS,cAAc,MAAkD;AACvE,QAAM,WAAW,MAAM,YAAY;AACnC,SAAO;AAAA,IACL,OAAO,MAAM,OAAO;AAAA,IAAI,OAAO,MAAM,SAAS;AAAA,IAAI,MAAM,MAAM,QAAQ;AAAA,IAAI,OAAO,MAAM,SAAS;AAAA,IAChG,OAAO,SAAS;AAAA,IAAO,UAAU,SAAS;AAAA,IAAU,YAAY,SAAS;AAAA,IACzE,GAAG,SAAS;AAAA,IAAQ,mBAAmB,CAAC,GAAG,SAAS,iBAAiB;AAAA,IAAG,WAAW,MAAM,aAAa;AAAA,EACxG;AACF;AAEO,SAAS,mBAAmB,EAAE,OAAO,YAAY,GAA2C;AACjG,QAAM,IAAI,KAAK;AACf,QAAM,eAAe,4BAA4B;AACjD,QAAM,EAAE,SAAS,qBAAqB,IAAI,iBAAiB;AAC3D,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAoC,CAAC,CAAC;AACtE,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAA0B,CAAC,CAAC;AAC1D,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAqD,MAAS;AACpG,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAqB,CAAC,CAAC;AAC/D,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAwC,SAAS;AACrG,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,CAAC;AAC9D,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAwC,SAAS;AACnF,QAAM,EAAE,aAAa,kBAAkB,IAAI,mBAAmB;AAAA,IAC5D,WAAW;AAAA,IACX,gBAAgB,EAAE,8BAA8B,4BAA4B;AAAA,EAC9E,CAAC;AACD,QAAM,OAAO,MAAM,YAAY,YAAY;AACzC,cAAU,SAAS;AACnB,QAAI;AACF,YAAM,CAAC,SAAS,UAAU,IAAI,MAAM,QAAQ,IAAI;AAAA,QAC9C,qBAA2D,+BAA+B;AAAA,QAC1F,qBAAiD,GAAG;AAAA,MACtD,CAAC;AACD,UAAI,CAAC,MAAM,QAAQ,QAAQ,KAAK,KAAK,CAAC,MAAM,QAAQ,WAAW,KAAK,EAAG,OAAM,IAAI,MAAM,2CAA2C;AAClI,eAAS,QAAQ,KAAK;AACtB,cAAQ,WAAW,KAAK;AACxB,gBAAU,OAAO;AAAA,IACnB,SAAS,KAAK;AACZ,aAAO,MAAM,iDAAiD,EAAE,IAAI,CAAC;AACrE,0BAAoB,GAAG,YAAY,KAAK;AAAA,QACtC,QAAQ;AAAA,QACR,MAAM;AAAA,MACR,CAAC;AACD,gBAAU,OAAO;AAAA,IACnB;AAAA,EACF,GAAG,CAAC,CAAC;AACL,QAAM,UAAU,MAAM;AAAE,SAAK,KAAK;AAAA,EAAE,GAAG,CAAC,MAAM,YAAY,CAAC;AAE3D,QAAM,UAAU,MAAM;AACpB,QAAI,aAAa,OAAW;AAC5B,QAAI,SAAS;AACb,uBAAmB,SAAS;AAC5B,SAAK;AAAA,MACH,sCAAsC,mBAAmB,SAAS,CAAC;AAAA,IACrE,EAAE,KAAK,CAAC,WAAW;AACjB,UAAI,CAAC,OAAQ;AACb,mBAAa,OAAO,oBAAoB,SAAS,KAAK,CAAC,CAAC;AACxD,yBAAmB,OAAO;AAAA,IAC5B,CAAC,EAAE,MAAM,CAAC,QAAQ;AAChB,aAAO,MAAM,kDAAkD,EAAE,IAAI,CAAC;AACtE,0BAAoB,GAAG,YAAY,KAAK;AAAA,QACtC,QAAQ;AAAA,QACR,MAAM;AAAA,MACR,CAAC;AACD,UAAI,OAAQ,oBAAmB,OAAO;AAAA,IACxC,CAAC;AACD,WAAO,MAAM;AAAE,eAAS;AAAA,IAAM;AAAA,EAChC,GAAG,CAAC,UAAU,eAAe,CAAC;AAE9B,QAAM,WAAW,CAAC,QAAgB,KAAK,KAAK,CAAC,QAAQ,IAAI,MAAM,YAAY,MAAM,OAAO,CAAC,IAAI,WAAW;AACxG,QAAM,SAAS,OAAO,SAAkC;AACtD,UAAM,MAAM,SAAS,KAAK,GAAG;AAC7B,QAAI,CAAC,IAAK;AACV,UAAM,WAAW,MAAM,QAAQ;AAAA,MAC7B,OAAO,EAAE,+CAA+C,+CAA+C;AAAA,MACvG,SAAS;AAAA,IACX,CAAC;AACD,QAAI,CAAC,SAAU;AACf,QAAI;AACF,YAAM,YAAY;AAAA,QAChB,WAAW,MAAM,4BAA4B,0BAA0B,IAAI,SAAS,GAAG,MACrF,eAAe,GAAG,GAAG,IAAI,mBAAmB,IAAI,EAAE,CAAC,IAAI,EAAE,QAAQ,SAAS,CAAC,CAAC;AAAA,QAC9E,SAAS,EAAE,QAAQ,4BAA4B,cAAc,wBAAwB,kBAAkB;AAAA,QACvG,iBAAiB,EAAE,QAAQ,UAAU,IAAI,IAAI,IAAI,MAAM,iBAAiB;AAAA,MAC1E,CAAC;AACD,YAAM,EAAE,0CAA0C,sCAAsC,GAAG,SAAS;AACpG,YAAM,KAAK;AAAA,IACb,SAAS,KAAK;AACZ,aAAO,MAAM,mDAAmD,EAAE,IAAI,CAAC;AACvE,0BAAoB,GAAG,YAAY,KAAK;AAAA,QACtC,QAAQ;AAAA,QACR,MAAM;AAAA,MACR,CAAC;AACD,UAAI,CAAC,sBAAsB,KAAK,GAAG,EAAE,WAAW,MAAM;AAAE,aAAK,KAAK;AAAA,MAAE,EAAE,CAAC,GAAG;AACxE,cAAM,EAAE,6CAA6C,+BAA+B,GAAG,OAAO;AAAA,MAChG;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,MAAM,QAAqB,MAAM;AAC9C,UAAM,QAAQ,CAAC,KAAa,aAAqB,EAAE,yCAAyC,GAAG,IAAI,QAAQ;AAC3G,UAAM,UAAU,CAAC,WAA8B,OAAO,IAAI,CAAC,WAAW,EAAE,OAAO,OAAO,EAAE,0CAA0C,KAAK,IAAI,KAAK,EAAE,EAAE;AACpJ,UAAM,iBAAiB,UAAU,IAAI,CAAC,EAAE,MAAM,OAAO,cAAc,OAAO,EAAE,OAAO,MAAM,OAAO,cAAc,EAAE;AAChH,UAAM,UAAU,UAAU,SAAS,kBAAkB,OAAO,CAAC,SAAS,CAAC,UAAU,KAAK,CAAC,aAAa,SAAS,SAAS,IAAI,CAAC,KAAK,CAAC;AACjI,WAAO;AAAA,MACL,EAAE,IAAI,SAAS,MAAM,QAAQ,OAAO,MAAM,SAAS,YAAY,GAAG,UAAU,MAAM,UAAU,CAAC,CAAC,SAAS;AAAA,MACvG,EAAE,IAAI,SAAS,MAAM,QAAQ,OAAO,MAAM,SAAS,OAAO,GAAG,UAAU,KAAK;AAAA,MAC5E,EAAE,IAAI,QAAQ,MAAM,QAAQ,OAAO,MAAM,QAAQ,MAAM,EAAE;AAAA,MACzD,EAAE,IAAI,SAAS,MAAM,QAAQ,OAAO,MAAM,SAAS,iBAAiB,EAAE;AAAA,MACtE,EAAE,IAAI,SAAS,MAAM,UAAU,OAAO,MAAM,SAAS,eAAe,GAAG,UAAU,KAAK;AAAA,MACtF,EAAE,IAAI,YAAY,MAAM,UAAU,OAAO,MAAM,YAAY,eAAe,GAAG,SAAS,QAAQ,CAAC,WAAW,QAAQ,SAAS,QAAQ,SAAS,MAAM,CAAC,EAAE;AAAA,MACrJ,EAAE,IAAI,cAAc,MAAM,YAAY,OAAO,MAAM,cAAc,0BAA0B,EAAE;AAAA,MAC7F,GAAI,CAAC,WAAW,UAAU,cAAc,YAAY,WAAW,EAAY,IAAI,CAAC,QAAmB,EAAE,IAAI,MAAM,YAAY,OAAO,MAAM,IAAI,EAAE,EAAE,EAAE;AAAA,MAClJ,EAAE,IAAI,YAAY,MAAM,UAAU,OAAO,MAAM,YAAY,gBAAgB,GAAG,SAAS,QAAQ,CAAC,QAAQ,YAAY,WAAW,CAAC,EAAE;AAAA,MAClI,EAAE,IAAI,UAAU,MAAM,UAAU,OAAO,MAAM,UAAU,cAAc,GAAG,SAAS,QAAQ,CAAC,QAAQ,aAAa,gBAAgB,cAAc,UAAU,CAAC,EAAE;AAAA,MAC1J;AAAA,QAAE,IAAI;AAAA,QAAqB,MAAM;AAAA,QAAU,UAAU;AAAA,QAAM,OAAO,MAAM,qBAAqB,wBAAwB;AAAA,QACnH,SAAS,CAAC,GAAG,gBAAgB,GAAG,QAAQ,IAAI,CAAC,UAAU,EAAE,OAAO,MAAM,OAAO,GAAG,IAAI,KAAK,EAAE,0CAA0C,SAAS,CAAC,IAAI,EAAE,CAAC;AAAA,MAAE;AAAA,IAC5J;AAAA,EACF,GAAG,CAAC,WAAW,UAAU,CAAC,CAAC;AAC3B,QAAM,SAA0B;AAAA,IAC9B,EAAE,IAAI,cAAc,OAAO,EAAE,6CAA6C,YAAY,GAAG,QAAQ,CAAC,SAAS,SAAS,QAAQ,SAAS,OAAO,EAAE;AAAA,IAC9I,EAAE,IAAI,YAAY,OAAO,EAAE,2CAA2C,eAAe,GAAG,QAAQ,CAAC,YAAY,cAAc,WAAW,UAAU,cAAc,YAAY,UAAU,YAAY,WAAW,EAAE;AAAA,IAC7M,GAAI,UAAU,UAAU,UAAU,SAAS,kBAAkB,SAAS,CAAC,EAAE,IAAI,aAAa,QAAQ,CAAC,mBAAmB,EAAE,CAAC,IAAI,CAAC;AAAA,EAChI;AACA,QAAM,OAAO,OAAO,WAAuB;AACzC,UAAM,MAAM,OAAO,OAAO,SAAS,EAAE,EAAE,KAAK;AAC5C,QAAI,CAAC,2BAA2B,UAAU,GAAG,EAAE,QAAS,OAAM,oBAAoB,EAAE,6CAA6C,qEAAqE,GAAG,EAAE,OAAO,EAAE,6CAA6C,qEAAqE,EAAE,CAAC;AACzU,QAAI,oBAAoB,QAAS,OAAM,oBAAoB,EAAE,uDAAuD,yDAAyD,CAAC;AAC9K,UAAM,WAAW,gCAAgC,UAAU;AAAA,MACzD,eAAe;AAAA,MAAG,UAAU,OAAO;AAAA,MAAU,YAAY,OAAO,eAAe;AAAA,MAC/E,OAAO,OAAO,OAAO,KAAK;AAAA,MAAG,mBAAmB,OAAO,qBAAqB,CAAC;AAAA,MAC7E,QAAQ;AAAA,QAAE,SAAS,OAAO,YAAY;AAAA,QAAM,QAAQ,OAAO,WAAW;AAAA,QAAM,YAAY,OAAO,eAAe;AAAA,QAC5G,UAAU,OAAO;AAAA,QAAU,QAAQ,OAAO;AAAA,QAAQ,UAAU,OAAO,aAAa;AAAA,QAAM,WAAW,OAAO,cAAc;AAAA,MAAK;AAAA,IAC/H,CAAC;AACD,QAAI,CAAC,SAAS,QAAS,OAAM,oBAAoB,EAAE,kDAAkD,iCAAiC,CAAC;AACvI,UAAM,UAAU;AAAA,MAAE,OAAO;AAAA,MAAK,OAAO,OAAO,OAAO,SAAS,EAAE,EAAE,KAAK;AAAA,MAAG,MAAM,OAAO,OAAO,QAAQ,EAAE,EAAE,KAAK,KAAK;AAAA,MAChH,OAAO,OAAO,OAAO,SAAS,EAAE,EAAE,KAAK,KAAK;AAAA,MAAM,UAAU,SAAS;AAAA,IAAK;AAC5E,UAAM,MAAM,WAAW,SAAS,SAAS,GAAG,IAAI;AAChD,UAAM,eAAe,MAAM,GAAG,GAAG,IAAI,mBAAmB,IAAI,EAAE,CAAC,KAAK,KAAK;AAAA,MACvE,QAAQ,MAAM,UAAU;AAAA,MAAQ,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9E,MAAM,KAAK,UAAU,MAAM,EAAE,OAAO,QAAQ,OAAO,MAAM,QAAQ,MAAM,OAAO,QAAQ,OAAO,UAAU,QAAQ,SAAS,IAAI,OAAO;AAAA,IACrI,CAAC;AACD,gBAAY,MAAS;AACrB,UAAM,EAAE,wCAAwC,sBAAsB,GAAG,SAAS;AAClF,UAAM,KAAK;AAAA,EACb;AAEA,QAAM,UAAU,MAAM,QAA8C,MAAM;AAAA,IACxE,EAAE,aAAa,SAAS,QAAQ,EAAE,+CAA+C,eAAe,GAAG,MAAM,CAAC,EAAE,IAAI,MAC9G,qBAAC,SAAI,WAAU,2BAA2B;AAAA,UAAI,SAAS,QAAQ,sBAAsB,IAAI,SAAS,OAAO,0CAA0C,IAAI;AAAA,MACpJ,IAAI,SAAS,OAAO,qBAAqB,IAAI,SAAS,MAAM,QAAQ,IAAI;AAAA,MAAK,oBAAC,UAAM,cAAI,SAAS,WAC9F,EAAE,IAAI,SAAS,UAAU,IAAI,SAAS,KAAK,IAC3C,IAAI,SAAS,OAAM;AAAA,OAAO,EAAQ;AAAA,IAC1C,EAAE,aAAa,OAAO,QAAQ,EAAE,8CAA8C,YAAY,EAAE;AAAA,IAC5F,EAAE,IAAI,UAAU,QAAQ,EAAE,iDAAiD,QAAQ,GAAG,MAAM,CAAC,EAAE,IAAI,MAAM,IAAI,SAAS,kBAClH,EAAE,wCAAwC,gBAAgB,IAAI,IAAI,SAAS,cACzE,EAAE,4CAA4C,WAAW,IAAI,IAAI,SAAS,OAAO;AAAA,IACvF,EAAE,IAAI,YAAY,QAAQ,EAAE,mDAAmD,UAAU,GAAG,MAAM,CAAC,EAAE,IAAI,MACvG,EAAE,0CAA0C,IAAI,SAAS,SAAS,QAAQ,IAAI,IAAI,SAAS,SAAS,QAAQ,EAAE;AAAA,IAChH,EAAE,IAAI,UAAU,QAAQ,EAAE,iDAAiD,YAAY,GAAG,MAAM,CAAC,EAAE,IAAI,MAAM,IAAI,SAAS,aACtH,EAAE,sCAAsC,KAAK,IAAI,EAAE,qCAAqC,IAAI,EAAE;AAAA,IAClG,EAAE,IAAI,UAAU,QAAQ,EAAE,iDAAiD,QAAQ,GAAG,MAAM,CAAC,EAAE,IAAI,MACjG,OAAO,OAAO,IAAI,SAAS,SAAS,MAAM,EAAE,OAAO,CAAC,UAAU,UAAU,SAAS,UAAU,MAAM,EAAE,OAAO;AAAA,IAC5G,EAAE,IAAI,aAAa,QAAQ,EAAE,oDAAoD,WAAW,GAAG,MAAM,CAAC,EAAE,IAAI,MAAM,IAAI,SAAS,SAAS,kBAAkB,OAAO;AAAA,IACjK,EAAE,IAAI,SAAS,QAAQ,EAAE,gDAAgD,OAAO,GAAG,MAAM,CAAC,EAAE,IAAI,MAAM,IAAI,SAAS,SAAS,MAAM;AAAA,EACpI,GAAG,CAAC,CAAC,CAAC;AAEN,SAAO,qBAAC,aAAQ,IAAI,uCAAuC,gBAAgB,GAAG,WAAU,sEACtF;AAAA,yBAAC,SAAI,WAAU,gCAA+B;AAAA,0BAAC,QAAG,WAAU,uBAAuB,iBAAM;AAAA,MAAK,oBAAC,OAAE,WAAU,iCAAiC,uBAAY;AAAA,OAAI;AAAA,IAC5J,oBAAC,SAAI,WAAU,qBACZ,qBAAW,UAAU,qBAAC,SAAI,WAAU,aAAY;AAAA,0BAAC,gBAAa,OAAO,EAAE,6CAA6C,gCAAgC,GAAG;AAAA,MAAE,oBAAC,UAAO,MAAK,UAAS,SAAS,MAAM;AAAE,aAAK,KAAK;AAAA,MAAE,GAAI,YAAE,wCAAwC,OAAO,GAAE;AAAA,OAAS,IAC3Q;AAAA,MAAC;AAAA;AAAA,QAAmC,UAAQ;AAAA,QAAC;AAAA,QAAkB,MAAM;AAAA,QAAO,WAAW,WAAW;AAAA,QAChG;AAAA,QAAc,mBAAmB;AAAA,QAAG,YAAY,EAAE,wCAAwC,8BAA8B;AAAA,QACxH,SAAS,oBAAC,UAAO,MAAK,UAAS,MAAK,MAAK,SAAS,MAAM,YAAY,IAAI,GAAI,YAAE,sCAAsC,mBAAmB,GAAE;AAAA,QACzI,eAAe,EAAE,OAAO,EAAE,0CAA0C,SAAS,GAAG,WAAW,MAAM,cAAc,WAAW,UAAU;AAAA,QACpI,YAAY,CAAC,SAAS;AACpB,gBAAM,UAA2B,CAAC,EAAE,IAAI,QAAQ,OAAO,KAAK,oBACxD,EAAE,4CAA4C,WAAW,IAAI,EAAE,uCAAuC,MAAM,GAAG,UAAU,MAAM,YAAY,IAAI,EAAE,CAAC;AACtJ,cAAI,SAAS,KAAK,GAAG,EAAG,SAAQ,KAAK;AAAA,YAAE,IAAI;AAAA,YAAU,OAAO,KAAK,WAAW,eACxE,EAAE,yCAAyC,QAAQ,IAAI,EAAE,wCAAwC,gBAAgB;AAAA,YACnH,aAAa;AAAA,YAAM,UAAU,MAAM;AAAE,mBAAK,OAAO,IAAI;AAAA,YAAE;AAAA,UAAE,CAAC;AAC5D,iBAAO,oBAAC,cAAW,OAAO,SAAS;AAAA,QACrC;AAAA;AAAA,IAAG,GACT;AAAA,IACA,oBAAC,UAAO,MAAM,aAAa,QAAW,cAAc,CAAC,SAAS;AAAE,UAAI,CAAC,KAAM,aAAY,MAAS;AAAA,IAAE,GAChG,+BAAC,iBAAc,WAAU,6CACvB;AAAA,0BAAC,gBAAa,8BAAC,eAAa,qBACxB,EAAE,4CAA4C,WAAW,IAAI,EAAE,sCAAsC,mBAAmB,GAAE,GAAc;AAAA,MAC3I,UAAU,cAAc,oBAAC,OAAE,WAAU,iCAAiC,YAAE,gDAAgD,wDAAwD,GAAE,IAAO;AAAA,MACzL,UAAU,6BAA6B,oBAAC,OAAE,WAAU,iCAAiC,YAAE,+CAA+C,+DAA+D,GAAE,IAAO;AAAA,MAC9M,UAAU,yBAAyB,SAAS,qBAAC,OAAE,WAAU,iCAAiC;AAAA,UAAE,8CAA8C,4CAA4C;AAAA,QAAE;AAAA,QAAG,SAAS,yBAAyB,KAAK,IAAI;AAAA,SAAE,IAAO;AAAA,MAC/O,oBAAoB,YAAY,oBAAC,kBAAe,OAAO,EAAE,mDAAmD,yBAAoB,GAAG,IAAK;AAAA,MACxI,oBAAoB,UAAU,oBAAC,gBAAa,OAAO,EAAE,uDAAuD,yDAAyD,GAAG,IAAK;AAAA,MAC7K,oBAAoB,UAAU,oBAAC,UAAO,MAAK,UAAS,SAAS,MAAM,mBAAmB,CAAC,YAAY,UAAU,CAAC,GAAI,YAAE,wCAAwC,OAAO,GAAE,IAAY;AAAA,MACjL,oBAAoB,WAAW,UAAU,WAAW,IAAI,oBAAC,OAAE,WAAU,iCAAiC,YAAE,8CAA8C,4CAA4C,GAAE,IAAO;AAAA,MAC3M,aAAa,UAAa,oBAAoB,UAAU;AAAA,QAAC;AAAA;AAAA,UAAkD,UAAQ;AAAA,UAClH,UAAU,UAAU,sBAAsB;AAAA,UAAO;AAAA,UAAgB;AAAA,UACjE,eAAe,cAAc,QAAQ;AAAA,UAAG,UAAU;AAAA,UAAM,aAAa,EAAE,uCAAuC,MAAM;AAAA,UACpH,cAAc,oBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,MAAM,YAAY,MAAS,GAAI,YAAE,+CAA+C,QAAQ,GAAE;AAAA;AAAA,QAHvE,UAAU,OAAO;AAAA,MAGiE,IAAK;AAAA,OAC7K,GACF;AAAA,IACC;AAAA,KACH;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import dynamic from "next/dynamic";
|
|
4
5
|
import {
|
|
5
6
|
Dialog,
|
|
6
7
|
DialogContent,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
} from "@open-mercato/core/modules/dictionaries/components/DictionaryTable";
|
|
27
28
|
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
28
29
|
const logger = createLogger("customers");
|
|
30
|
+
const ActivityTypeEditor = dynamic(() => import("./ActivityTypeEditor.js").then((module) => module.ActivityTypeEditor), { ssr: false });
|
|
29
31
|
const DEFAULT_FORM_VALUES = {
|
|
30
32
|
value: "",
|
|
31
33
|
label: "",
|
|
@@ -136,7 +138,7 @@ function DictionarySettings() {
|
|
|
136
138
|
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: t("customers.config.dictionaries.title", "Customers dictionaries") }),
|
|
137
139
|
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("customers.config.dictionaries.description", "Manage the dictionaries used by the customers module.") })
|
|
138
140
|
] }),
|
|
139
|
-
/* @__PURE__ */ jsx("div", { className: "space-y-6", children: sections.map((section) => /* @__PURE__ */ jsx(CustomerDictionarySection, { ...section }, section.kind)) })
|
|
141
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-6", children: sections.map((section) => section.kind === "activity-types" ? /* @__PURE__ */ jsx(ActivityTypeEditor, { title: section.title, description: section.description }, section.kind) : /* @__PURE__ */ jsx(CustomerDictionarySection, { ...section }, section.kind)) })
|
|
140
142
|
] });
|
|
141
143
|
}
|
|
142
144
|
function CustomerDictionarySection({ kind, title, description }) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/customers/components/DictionarySettings.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport {\n getCustomerDictionarySettingsSectionId,\n type CustomerDictionaryKind,\n} from '../lib/dictionaries'\nimport { ICON_SUGGESTIONS } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport {\n DictionaryForm,\n type DictionaryFormValues,\n} from '@open-mercato/core/modules/dictionaries/components/DictionaryForm'\nimport {\n DictionaryTable,\n type DictionaryTableEntry,\n} from '@open-mercato/core/modules/dictionaries/components/DictionaryTable'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\n\ntype SectionDefinition = {\n kind: CustomerDictionaryKind\n title: string\n description: string\n}\n\ntype DictionaryDeleteError = Error & {\n code?: string\n usageCount?: number\n}\n\ntype DialogState =\n | { mode: 'create' }\n | { mode: 'edit'; entry: DictionaryTableEntry }\n\nconst DEFAULT_FORM_VALUES: DictionaryFormValues = {\n value: '',\n label: '',\n color: null,\n icon: null,\n}\n\nconst DICTIONARY_HASH_SCROLL_RETRY_DELAYS_MS = [100, 350, 1000] as const\n\nfunction getCurrentDictionaryHashTarget(): HTMLElement | null {\n if (typeof window === 'undefined') return null\n const hash = window.location.hash\n if (!hash.startsWith('#customer-dictionary-')) return null\n try {\n return document.getElementById(decodeURIComponent(hash.slice(1)))\n } catch {\n return document.getElementById(hash.slice(1))\n }\n}\n\nexport default function DictionarySettings() {\n const t = useT()\n\n const sections = React.useMemo<SectionDefinition[]>(() => [\n {\n kind: 'person-company-roles',\n title: t('customers.config.dictionaries.sections.personCompanyRoles.title', 'Role types'),\n description: t('customers.config.dictionaries.sections.personCompanyRoles.description', 'Manage the ownership roles available in People tab assignments.'),\n },\n {\n kind: 'statuses',\n title: t('customers.config.dictionaries.sections.statuses.title', 'Statuses'),\n description: t('customers.config.dictionaries.sections.statuses.description', 'Define the statuses available for customer records.'),\n },\n {\n kind: 'deal-statuses',\n title: t('customers.config.dictionaries.sections.dealStatuses.title', 'Deal statuses'),\n description: t('customers.config.dictionaries.sections.dealStatuses.description', 'Manage the statuses available for deals.'),\n },\n {\n kind: 'job-titles',\n title: t('customers.config.dictionaries.sections.jobTitles.title', 'Job titles'),\n description: t('customers.config.dictionaries.sections.jobTitles.description', 'Configure job titles with their appearance.'),\n },\n {\n kind: 'sources',\n title: t('customers.config.dictionaries.sections.sources.title', 'Sources'),\n description: t('customers.config.dictionaries.sections.sources.description', 'Capture how customers were acquired.'),\n },\n {\n kind: 'industries',\n title: t('customers.config.dictionaries.sections.industries.title', 'Industries'),\n description: t('customers.config.dictionaries.sections.industries.description', 'Manage the industries used by companies.'),\n },\n {\n kind: 'lifecycle-stages',\n title: t('customers.config.dictionaries.sections.lifecycle.title', 'Lifecycle stages'),\n description: t('customers.config.dictionaries.sections.lifecycle.description', 'Configure lifecycle stages to track customer progress.'),\n },\n {\n kind: 'activity-types',\n title: t('customers.config.dictionaries.sections.activityTypes.title', 'Activity types'),\n description: t('customers.config.dictionaries.sections.activityTypes.description', 'Define the activity types used for customer interactions.'),\n },\n {\n kind: 'interaction-statuses',\n title: t('customers.config.dictionaries.sections.interactionStatuses.title', 'Interaction statuses'),\n description: t('customers.config.dictionaries.sections.interactionStatuses.description', 'Manage the statuses available for tasks and logged interactions.'),\n },\n {\n kind: 'address-types',\n title: t('customers.config.dictionaries.sections.addressTypes.title', 'Address types'),\n description: t('customers.config.dictionaries.sections.addressTypes.description', 'Define the available address types.'),\n },\n ], [t])\n\n React.useEffect(() => {\n const timeoutIds: number[] = []\n let frameId: number | null = null\n\n const clearScheduledScrolls = () => {\n if (frameId !== null) {\n window.cancelAnimationFrame(frameId)\n frameId = null\n }\n while (timeoutIds.length > 0) {\n const timeoutId = timeoutIds.pop()\n if (timeoutId !== undefined) window.clearTimeout(timeoutId)\n }\n }\n\n const scrollToHashTarget = () => {\n getCurrentDictionaryHashTarget()?.scrollIntoView({ block: 'start' })\n }\n\n const scheduleHashScroll = () => {\n clearScheduledScrolls()\n if (!window.location.hash.startsWith('#customer-dictionary-')) return\n scrollToHashTarget()\n if (typeof window.requestAnimationFrame === 'function') {\n frameId = window.requestAnimationFrame(scrollToHashTarget)\n }\n DICTIONARY_HASH_SCROLL_RETRY_DELAYS_MS.forEach((delay) => {\n timeoutIds.push(window.setTimeout(scrollToHashTarget, delay))\n })\n }\n\n scheduleHashScroll()\n window.addEventListener('hashchange', scheduleHashScroll)\n return () => {\n window.removeEventListener('hashchange', scheduleHashScroll)\n clearScheduledScrolls()\n }\n }, [])\n\n return (\n <div className=\"space-y-8\">\n <header className=\"space-y-2\">\n <h1 className=\"text-2xl font-semibold\">\n {t('customers.config.dictionaries.title', 'Customers dictionaries')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t('customers.config.dictionaries.description', 'Manage the dictionaries used by the customers module.')}\n </p>\n </header>\n\n <div className=\"space-y-6\">\n {sections.map((section) => (\n <CustomerDictionarySection key={section.kind} {...section} />\n ))}\n </div>\n </div>\n )\n}\n\ntype CustomerDictionarySectionProps = SectionDefinition\n\nfunction CustomerDictionarySection({ kind, title, description }: CustomerDictionarySectionProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const scopeVersion = useOrganizationScopeVersion()\n const [entries, setEntries] = React.useState<DictionaryTableEntry[]>([])\n const [loading, setLoading] = React.useState<boolean>(true)\n const [dialog, setDialog] = React.useState<DialogState | null>(null)\n const [submitting, setSubmitting] = React.useState(false)\n\n const saveContextId = `customers-dictionary-${kind}`\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: saveContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const mutationContext = React.useMemo(\n () => ({\n formId: saveContextId,\n resourceKind: 'customers.dictionary',\n retryLastMutation,\n }),\n [retryLastMutation, saveContextId],\n )\n\n const inheritedActionBlocked = t('customers.config.dictionaries.inherited.blocked', 'Inherited entries can only be edited from the parent organization.')\n const inheritedTooltip = t('customers.config.dictionaries.inherited.tooltip', 'Managed in parent organization')\n const inheritedLabel = t('customers.config.dictionaries.inherited.label', 'Inherited')\n const errorLoad = t('customers.config.dictionaries.error.load', 'Failed to load dictionary entries.')\n const errorSave = t('customers.config.dictionaries.error.save', 'Failed to save dictionary entry.')\n const errorDelete = t('customers.config.dictionaries.error.delete', 'Failed to delete dictionary entry.')\n const successSave = t('customers.config.dictionaries.success.save', 'Dictionary entry saved.')\n const successDelete = t('customers.config.dictionaries.success.delete', 'Dictionary entry deleted.')\n const deleteConfirmTemplate = t('customers.config.dictionaries.deleteConfirm', 'Delete \"{{value}}\"?')\n const roleTypeDeleteBlockedTitle = t('customers.config.dictionaries.roleTypes.deleteBlocked.title', 'Role type is in use')\n const roleTypeDeleteBlockedText = t(\n 'customers.config.dictionaries.roleTypes.deleteBlocked.text',\n 'This role type is assigned to {{count}} records. Remove or replace those assignments before deleting it.',\n )\n const usageCountLabel = t('customers.config.dictionaries.roleTypes.usageCount', 'In use on {{count}} records')\n const searchPlaceholder = t('customers.config.dictionaries.searchPlaceholder', 'Search entries\u2026')\n\n const formatCountMessage = React.useCallback((template: string, count: number) => {\n return template.replace('{{count}}', String(count))\n }, [])\n\n const loadEntries = React.useCallback(async () => {\n setLoading(true)\n try {\n const data = await readApiResultOrThrow<{ items?: unknown[] }>(\n `/api/customers/dictionaries/${kind}`,\n undefined,\n { errorMessage: errorLoad },\n )\n if (!Array.isArray(data?.items)) throw new Error(errorLoad)\n const mapped: DictionaryTableEntry[] = data.items.map((item: any) => ({\n id: String(item.id),\n value: String(item.value ?? ''),\n label: typeof item.label === 'string' ? item.label : '',\n color: typeof item.color === 'string' ? item.color : null,\n icon: typeof item.icon === 'string' ? item.icon : null,\n usageCount: typeof item.usageCount === 'number' ? item.usageCount : undefined,\n organizationId: typeof item.organizationId === 'string' ? item.organizationId : null,\n tenantId: typeof item.tenantId === 'string' ? item.tenantId : null,\n isInherited: item.isInherited === true,\n createdAt: typeof item.createdAt === 'string' ? item.createdAt : null,\n updatedAt: typeof item.updatedAt === 'string' ? item.updatedAt : null,\n }))\n setEntries(mapped)\n } catch (err) {\n logger.error('customers.dictionaries.list failed', { err })\n flash(errorLoad, 'error')\n } finally {\n setLoading(false)\n }\n }, [errorLoad, kind, scopeVersion])\n\n React.useEffect(() => {\n loadEntries().catch(() => {})\n }, [loadEntries])\n\n const closeDialog = React.useCallback(() => {\n setDialog(null)\n }, [])\n\n const handleCreate = React.useCallback(() => {\n setDialog({ mode: 'create' })\n }, [])\n\n const handleEdit = React.useCallback((entry: DictionaryTableEntry) => {\n if (entry.isInherited) {\n flash(inheritedActionBlocked, 'info')\n return\n }\n setDialog({ mode: 'edit', entry })\n }, [inheritedActionBlocked])\n\n const handleDelete = React.useCallback(async (entry: DictionaryTableEntry) => {\n if (entry.isInherited) {\n flash(inheritedActionBlocked, 'info')\n return\n }\n if (kind === 'person-company-roles' && (entry.usageCount ?? 0) > 0) {\n await confirm({\n title: roleTypeDeleteBlockedTitle,\n description: formatCountMessage(roleTypeDeleteBlockedText, entry.usageCount ?? 0),\n confirmText: false,\n cancelText: t('customers.config.dictionaries.dialog.cancel', 'Cancel'),\n })\n return\n }\n const message = deleteConfirmTemplate.replace('{{value}}', entry.label || entry.value)\n const confirmed = await confirm({\n title: message,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(entry.updatedAt), () =>\n apiCallOrThrow(\n `/api/customers/dictionaries/${kind}/${encodeURIComponent(entry.id)}`,\n { method: 'DELETE' },\n { errorMessage: errorDelete },\n ),\n ),\n context: mutationContext,\n mutationPayload: { action: 'delete', id: entry.id, kind },\n })\n flash(successDelete, 'success')\n await loadEntries()\n } catch (err) {\n logger.error('customers.dictionaries.delete failed', { err })\n if (kind === 'person-company-roles' && (err as DictionaryDeleteError)?.code === 'role_type_in_use') {\n const usageCount = Number((err as DictionaryDeleteError).usageCount ?? 0)\n await confirm({\n title: roleTypeDeleteBlockedTitle,\n description: formatCountMessage(roleTypeDeleteBlockedText, usageCount),\n confirmText: false,\n cancelText: t('customers.config.dictionaries.dialog.cancel', 'Cancel'),\n })\n return\n }\n const messageValue = err instanceof Error ? err.message : errorDelete\n flash(messageValue, 'error')\n }\n }, [confirm, deleteConfirmTemplate, errorDelete, formatCountMessage, inheritedActionBlocked, kind, loadEntries, mutationContext, roleTypeDeleteBlockedText, roleTypeDeleteBlockedTitle, runMutation, successDelete, t])\n\n const submitForm = React.useCallback(async (values: DictionaryFormValues) => {\n const payload = {\n value: values.value,\n label: values.label,\n color: values.color,\n icon: values.icon,\n }\n setSubmitting(true)\n try {\n if (!dialog || dialog.mode === 'create') {\n await runMutation({\n operation: () =>\n apiCallOrThrow(\n `/api/customers/dictionaries/${kind}`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n { errorMessage: errorSave },\n ),\n context: mutationContext,\n mutationPayload: { action: 'create', kind, ...payload },\n })\n flash(successSave, 'success')\n } else if (dialog.mode === 'edit') {\n const target = dialog.entry\n if (target.isInherited) {\n flash(inheritedActionBlocked, 'info')\n return\n }\n const body: Record<string, unknown> = {}\n if (values.value !== target.value) body.value = values.value\n if (values.label !== target.label) body.label = values.label\n const nextColor = values.color ?? null\n if (nextColor !== (target.color ?? null)) body.color = nextColor\n const nextIcon = values.icon ?? null\n if (nextIcon !== (target.icon ?? null)) body.icon = nextIcon\n if (Object.keys(body).length === 0) {\n closeDialog()\n return\n }\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(target.updatedAt), () =>\n apiCallOrThrow(\n `/api/customers/dictionaries/${kind}/${encodeURIComponent(target.id)}`,\n {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n },\n { errorMessage: errorSave },\n ),\n ),\n context: mutationContext,\n mutationPayload: { action: 'update', id: target.id, kind, ...body },\n })\n flash(successSave, 'success')\n }\n closeDialog()\n await loadEntries()\n } catch (err) {\n logger.error('customers.dictionaries.submit failed', { err })\n throw err instanceof Error ? err : new Error(errorSave)\n } finally {\n setSubmitting(false)\n }\n }, [closeDialog, dialog, errorSave, inheritedActionBlocked, kind, loadEntries, mutationContext, runMutation, successSave])\n\n const currentValues = React.useMemo<DictionaryFormValues>(() => {\n if (dialog && dialog.mode === 'edit') {\n return {\n value: dialog.entry.value,\n label: dialog.entry.label,\n color: dialog.entry.color,\n icon: dialog.entry.icon,\n }\n }\n return DEFAULT_FORM_VALUES\n }, [dialog])\n\n const tableTranslations = React.useMemo(() => ({\n title,\n valueColumn: t('customers.config.dictionaries.columns.value', 'Value'),\n labelColumn: t('customers.config.dictionaries.columns.label', 'Label'),\n appearanceColumn: t('customers.config.dictionaries.columns.appearance', 'Appearance'),\n addLabel: t('customers.config.dictionaries.actions.add', 'Add entry'),\n editLabel: t('customers.config.dictionaries.actions.edit', 'Edit'),\n deleteLabel: t('customers.config.dictionaries.actions.delete', 'Delete'),\n refreshLabel: t('customers.config.dictionaries.actions.refresh', 'Refresh'),\n inheritedLabel,\n inheritedTooltip,\n emptyLabel: t('customers.config.dictionaries.empty', 'No entries yet.'),\n usageCountLabel: kind === 'person-company-roles' ? usageCountLabel : undefined,\n searchPlaceholder,\n }), [inheritedLabel, inheritedTooltip, kind, searchPlaceholder, t, title, usageCountLabel])\n\n const formTranslations = React.useMemo(() => ({\n title: dialog?.mode === 'edit'\n ? t('customers.config.dictionaries.dialog.editTitle', 'Edit entry')\n : t('customers.config.dictionaries.dialog.addTitle', 'Add entry'),\n valueLabel: t('customers.config.dictionaries.dialog.valueLabel', 'Value'),\n labelLabel: t('customers.config.dictionaries.dialog.labelLabel', 'Label'),\n saveLabel: t('customers.config.dictionaries.dialog.save', 'Save'),\n cancelLabel: t('customers.config.dictionaries.dialog.cancel', 'Cancel'),\n appearance: {\n colorLabel: t('customers.config.dictionaries.dialog.colorLabel', 'Color'),\n colorHelp: t('customers.config.dictionaries.dialog.colorHelp', 'Pick a highlight color for this entry.'),\n colorClearLabel: t('customers.config.dictionaries.dialog.colorClear', 'Remove color'),\n iconLabel: t('customers.config.dictionaries.dialog.iconLabel', 'Icon'),\n iconPlaceholder: t('customers.config.dictionaries.dialog.iconPlaceholder', 'Type an emoji or pick one of the suggestions.'),\n iconPickerTriggerLabel: t('customers.config.dictionaries.dialog.iconBrowse', 'Browse icons and emojis'),\n iconSearchPlaceholder: t('customers.config.dictionaries.dialog.iconSearchPlaceholder', 'Search icons or emojis\u2026'),\n iconSearchEmptyLabel: t('customers.config.dictionaries.dialog.iconSearchEmpty', 'No icons match your search.'),\n iconSuggestionsLabel: t('customers.config.dictionaries.dialog.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('customers.config.dictionaries.dialog.iconClear', 'Remove icon'),\n previewEmptyLabel: t('customers.config.dictionaries.appearance.empty', 'None'),\n },\n }), [dialog, t])\n\n return (\n <section\n id={getCustomerDictionarySettingsSectionId(kind)}\n className=\"scroll-mt-24 rounded border bg-card text-card-foreground shadow-sm\"\n >\n <div className=\"border-b px-6 py-4 space-y-1\">\n <h2 className=\"text-lg font-medium\">{title}</h2>\n <p className=\"text-sm text-muted-foreground\">{description}</p>\n </div>\n <div className=\"px-2 py-4 sm:px-4\">\n <DictionaryTable\n entries={entries}\n loading={loading}\n canManage\n onCreate={handleCreate}\n onEdit={handleEdit}\n onDelete={handleDelete}\n onRefresh={loadEntries}\n translations={tableTranslations}\n />\n </div>\n <Dialog open={dialog !== null} onOpenChange={(open) => { if (!open) closeDialog() }}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>{formTranslations.title}</DialogTitle>\n </DialogHeader>\n <DictionaryForm\n mode={dialog?.mode === 'edit' ? 'edit' : 'create'}\n initialValues={currentValues}\n onSubmit={submitForm}\n onCancel={closeDialog}\n submitting={submitting}\n translations={formTranslations}\n iconSuggestions={ICON_SUGGESTIONS}\n />\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </section>\n )\n}\n"],
|
|
5
|
-
"mappings": ";AAqKM,SACE,KADF;AAnKN,YAAY,WAAW;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,aAAa;AACtB,SAAS,gBAAgB,sBAAsB,mCAAmC;AAClF,SAAS,iCAAiC;AAC1C,SAAS,0BAA0B;AACnC,SAAS,mCAAmC;AAC5C,SAAS,YAAY;AACrB,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,OAEK;AACP,SAAS,wBAAwB;AACjC;AAAA,EACE;AAAA,OAEK;AACP;AAAA,EACE;AAAA,OAEK;AACP,SAAS,oBAAoB;AAE7B,MAAM,SAAS,aAAa,WAAW;AAiBvC,MAAM,sBAA4C;AAAA,EAChD,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AAAA,EACP,MAAM;AACR;AAEA,MAAM,yCAAyC,CAAC,KAAK,KAAK,GAAI;AAE9D,SAAS,iCAAqD;AAC5D,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,QAAM,OAAO,OAAO,SAAS;AAC7B,MAAI,CAAC,KAAK,WAAW,uBAAuB,EAAG,QAAO;AACtD,MAAI;AACF,WAAO,SAAS,eAAe,mBAAmB,KAAK,MAAM,CAAC,CAAC,CAAC;AAAA,EAClE,QAAQ;AACN,WAAO,SAAS,eAAe,KAAK,MAAM,CAAC,CAAC;AAAA,EAC9C;AACF;AAEe,SAAR,qBAAsC;AAC3C,QAAM,IAAI,KAAK;AAEf,QAAM,WAAW,MAAM,QAA6B,MAAM;AAAA,IACxD;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,mEAAmE,YAAY;AAAA,MACxF,aAAa,EAAE,yEAAyE,iEAAiE;AAAA,IAC3J;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,yDAAyD,UAAU;AAAA,MAC5E,aAAa,EAAE,+DAA+D,qDAAqD;AAAA,IACrI;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,6DAA6D,eAAe;AAAA,MACrF,aAAa,EAAE,mEAAmE,0CAA0C;AAAA,IAC9H;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,0DAA0D,YAAY;AAAA,MAC/E,aAAa,EAAE,gEAAgE,6CAA6C;AAAA,IAC9H;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,wDAAwD,SAAS;AAAA,MAC1E,aAAa,EAAE,8DAA8D,sCAAsC;AAAA,IACrH;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,2DAA2D,YAAY;AAAA,MAChF,aAAa,EAAE,iEAAiE,0CAA0C;AAAA,IAC5H;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,0DAA0D,kBAAkB;AAAA,MACrF,aAAa,EAAE,gEAAgE,wDAAwD;AAAA,IACzI;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,8DAA8D,gBAAgB;AAAA,MACvF,aAAa,EAAE,oEAAoE,2DAA2D;AAAA,IAChJ;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,oEAAoE,sBAAsB;AAAA,MACnG,aAAa,EAAE,0EAA0E,kEAAkE;AAAA,IAC7J;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,OAAO,EAAE,6DAA6D,eAAe;AAAA,MACrF,aAAa,EAAE,mEAAmE,qCAAqC;AAAA,IACzH;AAAA,EACF,GAAG,CAAC,CAAC,CAAC;AAEN,QAAM,UAAU,MAAM;AACpB,UAAM,aAAuB,CAAC;AAC9B,QAAI,UAAyB;AAE7B,UAAM,wBAAwB,MAAM;AAClC,UAAI,YAAY,MAAM;AACpB,eAAO,qBAAqB,OAAO;AACnC,kBAAU;AAAA,MACZ;AACA,aAAO,WAAW,SAAS,GAAG;AAC5B,cAAM,YAAY,WAAW,IAAI;AACjC,YAAI,cAAc,OAAW,QAAO,aAAa,SAAS;AAAA,MAC5D;AAAA,IACF;AAEA,UAAM,qBAAqB,MAAM;AAC/B,qCAA+B,GAAG,eAAe,EAAE,OAAO,QAAQ,CAAC;AAAA,IACrE;AAEA,UAAM,qBAAqB,MAAM;AAC/B,4BAAsB;AACtB,UAAI,CAAC,OAAO,SAAS,KAAK,WAAW,uBAAuB,EAAG;AAC/D,yBAAmB;AACnB,UAAI,OAAO,OAAO,0BAA0B,YAAY;AACtD,kBAAU,OAAO,sBAAsB,kBAAkB;AAAA,MAC3D;AACA,6CAAuC,QAAQ,CAAC,UAAU;AACxD,mBAAW,KAAK,OAAO,WAAW,oBAAoB,KAAK,CAAC;AAAA,MAC9D,CAAC;AAAA,IACH;AAEA,uBAAmB;AACnB,WAAO,iBAAiB,cAAc,kBAAkB;AACxD,WAAO,MAAM;AACX,aAAO,oBAAoB,cAAc,kBAAkB;AAC3D,4BAAsB;AAAA,IACxB;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,yBAAC,YAAO,WAAU,aAChB;AAAA,0BAAC,QAAG,WAAU,0BACX,YAAE,uCAAuC,wBAAwB,GACpE;AAAA,MACA,oBAAC,OAAE,WAAU,iCACV,YAAE,6CAA6C,uDAAuD,GACzG;AAAA,OACF;AAAA,IAEA,oBAAC,SAAI,WAAU,aACZ,mBAAS,IAAI,CAAC,YACb,oBAAC,6BAA8C,GAAG,WAAlB,QAAQ,IAAmB,CAC5D,GACH;AAAA,KACF;AAEJ;AAIA,SAAS,0BAA0B,EAAE,MAAM,OAAO,YAAY,GAAmC;AAC/F,QAAM,IAAI,KAAK;AACf,QAAM,EAAE,SAAS,qBAAqB,IAAI,iBAAiB;AAC3D,QAAM,eAAe,4BAA4B;AACjD,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAiC,CAAC,CAAC;AACvE,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAkB,IAAI;AAC1D,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAA6B,IAAI;AACnE,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,KAAK;AAExD,QAAM,gBAAgB,wBAAwB,IAAI;AAClD,QAAM,EAAE,aAAa,kBAAkB,IAAI,mBAIxC;AAAA,IACD,WAAW;AAAA,IACX,gBAAgB,EAAE,8BAA8B,4BAA4B;AAAA,EAC9E,CAAC;AACD,QAAM,kBAAkB,MAAM;AAAA,IAC5B,OAAO;AAAA,MACL,QAAQ;AAAA,MACR,cAAc;AAAA,MACd;AAAA,IACF;AAAA,IACA,CAAC,mBAAmB,aAAa;AAAA,EACnC;AAEA,QAAM,yBAAyB,EAAE,mDAAmD,oEAAoE;AACxJ,QAAM,mBAAmB,EAAE,mDAAmD,gCAAgC;AAC9G,QAAM,iBAAiB,EAAE,iDAAiD,WAAW;AACrF,QAAM,YAAY,EAAE,4CAA4C,oCAAoC;AACpG,QAAM,YAAY,EAAE,4CAA4C,kCAAkC;AAClG,QAAM,cAAc,EAAE,8CAA8C,oCAAoC;AACxG,QAAM,cAAc,EAAE,8CAA8C,yBAAyB;AAC7F,QAAM,gBAAgB,EAAE,gDAAgD,2BAA2B;AACnG,QAAM,wBAAwB,EAAE,+CAA+C,qBAAqB;AACpG,QAAM,6BAA6B,EAAE,+DAA+D,qBAAqB;AACzH,QAAM,4BAA4B;AAAA,IAChC;AAAA,IACA;AAAA,EACF;AACA,QAAM,kBAAkB,EAAE,sDAAsD,6BAA6B;AAC7G,QAAM,oBAAoB,EAAE,mDAAmD,sBAAiB;AAEhG,QAAM,qBAAqB,MAAM,YAAY,CAAC,UAAkB,UAAkB;AAChF,WAAO,SAAS,QAAQ,aAAa,OAAO,KAAK,CAAC;AAAA,EACpD,GAAG,CAAC,CAAC;AAEL,QAAM,cAAc,MAAM,YAAY,YAAY;AAChD,eAAW,IAAI;AACf,QAAI;AACF,YAAM,OAAO,MAAM;AAAA,QACjB,+BAA+B,IAAI;AAAA,QACnC;AAAA,QACA,EAAE,cAAc,UAAU;AAAA,MAC5B;AACA,UAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,EAAG,OAAM,IAAI,MAAM,SAAS;AAC1D,YAAM,SAAiC,KAAK,MAAM,IAAI,CAAC,UAAe;AAAA,QACpE,IAAI,OAAO,KAAK,EAAE;AAAA,QAClB,OAAO,OAAO,KAAK,SAAS,EAAE;AAAA,QAC9B,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAAA,QACrD,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAAA,QACrD,MAAM,OAAO,KAAK,SAAS,WAAW,KAAK,OAAO;AAAA,QAClD,YAAY,OAAO,KAAK,eAAe,WAAW,KAAK,aAAa;AAAA,QACpE,gBAAgB,OAAO,KAAK,mBAAmB,WAAW,KAAK,iBAAiB;AAAA,QAChF,UAAU,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW;AAAA,QAC9D,aAAa,KAAK,gBAAgB;AAAA,QAClC,WAAW,OAAO,KAAK,cAAc,WAAW,KAAK,YAAY;AAAA,QACjE,WAAW,OAAO,KAAK,cAAc,WAAW,KAAK,YAAY;AAAA,MACnE,EAAE;AACF,iBAAW,MAAM;AAAA,IACnB,SAAS,KAAK;AACZ,aAAO,MAAM,sCAAsC,EAAE,IAAI,CAAC;AAC1D,YAAM,WAAW,OAAO;AAAA,IAC1B,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,WAAW,MAAM,YAAY,CAAC;AAElC,QAAM,UAAU,MAAM;AACpB,gBAAY,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC9B,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,cAAc,MAAM,YAAY,MAAM;AAC1C,cAAU,IAAI;AAAA,EAChB,GAAG,CAAC,CAAC;AAEL,QAAM,eAAe,MAAM,YAAY,MAAM;AAC3C,cAAU,EAAE,MAAM,SAAS,CAAC;AAAA,EAC9B,GAAG,CAAC,CAAC;AAEL,QAAM,aAAa,MAAM,YAAY,CAAC,UAAgC;AACpE,QAAI,MAAM,aAAa;AACrB,YAAM,wBAAwB,MAAM;AACpC;AAAA,IACF;AACA,cAAU,EAAE,MAAM,QAAQ,MAAM,CAAC;AAAA,EACnC,GAAG,CAAC,sBAAsB,CAAC;AAE3B,QAAM,eAAe,MAAM,YAAY,OAAO,UAAgC;AAC5E,QAAI,MAAM,aAAa;AACrB,YAAM,wBAAwB,MAAM;AACpC;AAAA,IACF;AACA,QAAI,SAAS,2BAA2B,MAAM,cAAc,KAAK,GAAG;AAClE,YAAM,QAAQ;AAAA,QACZ,OAAO;AAAA,QACP,aAAa,mBAAmB,2BAA2B,MAAM,cAAc,CAAC;AAAA,QAChF,aAAa;AAAA,QACb,YAAY,EAAE,+CAA+C,QAAQ;AAAA,MACvE,CAAC;AACD;AAAA,IACF;AACA,UAAM,UAAU,sBAAsB,QAAQ,aAAa,MAAM,SAAS,MAAM,KAAK;AACrF,UAAM,YAAY,MAAM,QAAQ;AAAA,MAC9B,OAAO;AAAA,MACP,SAAS;AAAA,IACX,CAAC;AACD,QAAI,CAAC,UAAW;AAChB,QAAI;AACF,YAAM,YAAY;AAAA,QAChB,WAAW,MACT;AAAA,UAA4B,0BAA0B,MAAM,SAAS;AAAA,UAAG,MACtE;AAAA,YACE,+BAA+B,IAAI,IAAI,mBAAmB,MAAM,EAAE,CAAC;AAAA,YACnE,EAAE,QAAQ,SAAS;AAAA,YACnB,EAAE,cAAc,YAAY;AAAA,UAC9B;AAAA,QACF;AAAA,QACF,SAAS;AAAA,QACT,iBAAiB,EAAE,QAAQ,UAAU,IAAI,MAAM,IAAI,KAAK;AAAA,MAC1D,CAAC;AACD,YAAM,eAAe,SAAS;AAC9B,YAAM,YAAY;AAAA,IACpB,SAAS,KAAK;AACZ,aAAO,MAAM,wCAAwC,EAAE,IAAI,CAAC;AAC5D,UAAI,SAAS,0BAA2B,KAA+B,SAAS,oBAAoB;AAClG,cAAM,aAAa,OAAQ,IAA8B,cAAc,CAAC;AACxE,cAAM,QAAQ;AAAA,UACZ,OAAO;AAAA,UACP,aAAa,mBAAmB,2BAA2B,UAAU;AAAA,UACrE,aAAa;AAAA,UACb,YAAY,EAAE,+CAA+C,QAAQ;AAAA,QACvE,CAAC;AACD;AAAA,MACF;AACA,YAAM,eAAe,eAAe,QAAQ,IAAI,UAAU;AAC1D,YAAM,cAAc,OAAO;AAAA,IAC7B;AAAA,EACF,GAAG,CAAC,SAAS,uBAAuB,aAAa,oBAAoB,wBAAwB,MAAM,aAAa,iBAAiB,2BAA2B,4BAA4B,aAAa,eAAe,CAAC,CAAC;AAEtN,QAAM,aAAa,MAAM,YAAY,OAAO,WAAiC;AAC3E,UAAM,UAAU;AAAA,MACd,OAAO,OAAO;AAAA,MACd,OAAO,OAAO;AAAA,MACd,OAAO,OAAO;AAAA,MACd,MAAM,OAAO;AAAA,IACf;AACA,kBAAc,IAAI;AAClB,QAAI;AACF,UAAI,CAAC,UAAU,OAAO,SAAS,UAAU;AACvC,cAAM,YAAY;AAAA,UAChB,WAAW,MACT;AAAA,YACE,+BAA+B,IAAI;AAAA,YACnC;AAAA,cACE,QAAQ;AAAA,cACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,cAC9C,MAAM,KAAK,UAAU,OAAO;AAAA,YAC9B;AAAA,YACA,EAAE,cAAc,UAAU;AAAA,UAC5B;AAAA,UACF,SAAS;AAAA,UACT,iBAAiB,EAAE,QAAQ,UAAU,MAAM,GAAG,QAAQ;AAAA,QACxD,CAAC;AACD,cAAM,aAAa,SAAS;AAAA,MAC9B,WAAW,OAAO,SAAS,QAAQ;AACjC,cAAM,SAAS,OAAO;AACtB,YAAI,OAAO,aAAa;AACtB,gBAAM,wBAAwB,MAAM;AACpC;AAAA,QACF;AACA,cAAM,OAAgC,CAAC;AACvC,YAAI,OAAO,UAAU,OAAO,MAAO,MAAK,QAAQ,OAAO;AACvD,YAAI,OAAO,UAAU,OAAO,MAAO,MAAK,QAAQ,OAAO;AACvD,cAAM,YAAY,OAAO,SAAS;AAClC,YAAI,eAAe,OAAO,SAAS,MAAO,MAAK,QAAQ;AACvD,cAAM,WAAW,OAAO,QAAQ;AAChC,YAAI,cAAc,OAAO,QAAQ,MAAO,MAAK,OAAO;AACpD,YAAI,OAAO,KAAK,IAAI,EAAE,WAAW,GAAG;AAClC,sBAAY;AACZ;AAAA,QACF;AACA,cAAM,YAAY;AAAA,UAChB,WAAW,MACT;AAAA,YAA4B,0BAA0B,OAAO,SAAS;AAAA,YAAG,MACvE;AAAA,cACE,+BAA+B,IAAI,IAAI,mBAAmB,OAAO,EAAE,CAAC;AAAA,cACpE;AAAA,gBACE,QAAQ;AAAA,gBACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,gBAC9C,MAAM,KAAK,UAAU,IAAI;AAAA,cAC3B;AAAA,cACA,EAAE,cAAc,UAAU;AAAA,YAC5B;AAAA,UACF;AAAA,UACF,SAAS;AAAA,UACT,iBAAiB,EAAE,QAAQ,UAAU,IAAI,OAAO,IAAI,MAAM,GAAG,KAAK;AAAA,QACpE,CAAC;AACD,cAAM,aAAa,SAAS;AAAA,MAC9B;AACA,kBAAY;AACZ,YAAM,YAAY;AAAA,IACpB,SAAS,KAAK;AACZ,aAAO,MAAM,wCAAwC,EAAE,IAAI,CAAC;AAC5D,YAAM,eAAe,QAAQ,MAAM,IAAI,MAAM,SAAS;AAAA,IACxD,UAAE;AACA,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,aAAa,QAAQ,WAAW,wBAAwB,MAAM,aAAa,iBAAiB,aAAa,WAAW,CAAC;AAEzH,QAAM,gBAAgB,MAAM,QAA8B,MAAM;AAC9D,QAAI,UAAU,OAAO,SAAS,QAAQ;AACpC,aAAO;AAAA,QACL,OAAO,OAAO,MAAM;AAAA,QACpB,OAAO,OAAO,MAAM;AAAA,QACpB,OAAO,OAAO,MAAM;AAAA,QACpB,MAAM,OAAO,MAAM;AAAA,MACrB;AAAA,IACF;AACA,WAAO;AAAA,EACT,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,oBAAoB,MAAM,QAAQ,OAAO;AAAA,IAC7C;AAAA,IACA,aAAa,EAAE,+CAA+C,OAAO;AAAA,IACrE,aAAa,EAAE,+CAA+C,OAAO;AAAA,IACrE,kBAAkB,EAAE,oDAAoD,YAAY;AAAA,IACpF,UAAU,EAAE,6CAA6C,WAAW;AAAA,IACpE,WAAW,EAAE,8CAA8C,MAAM;AAAA,IACjE,aAAa,EAAE,gDAAgD,QAAQ;AAAA,IACvE,cAAc,EAAE,iDAAiD,SAAS;AAAA,IAC1E;AAAA,IACA;AAAA,IACA,YAAY,EAAE,uCAAuC,iBAAiB;AAAA,IACtE,iBAAiB,SAAS,yBAAyB,kBAAkB;AAAA,IACrE;AAAA,EACF,IAAI,CAAC,gBAAgB,kBAAkB,MAAM,mBAAmB,GAAG,OAAO,eAAe,CAAC;AAE1F,QAAM,mBAAmB,MAAM,QAAQ,OAAO;AAAA,IAC5C,OAAO,QAAQ,SAAS,SACpB,EAAE,kDAAkD,YAAY,IAChE,EAAE,iDAAiD,WAAW;AAAA,IAClE,YAAY,EAAE,mDAAmD,OAAO;AAAA,IACxE,YAAY,EAAE,mDAAmD,OAAO;AAAA,IACxE,WAAW,EAAE,6CAA6C,MAAM;AAAA,IAChE,aAAa,EAAE,+CAA+C,QAAQ;AAAA,IACtE,YAAY;AAAA,MACV,YAAY,EAAE,mDAAmD,OAAO;AAAA,MACxE,WAAW,EAAE,kDAAkD,wCAAwC;AAAA,MACvG,iBAAiB,EAAE,mDAAmD,cAAc;AAAA,MACpF,WAAW,EAAE,kDAAkD,MAAM;AAAA,MACrE,iBAAiB,EAAE,wDAAwD,+CAA+C;AAAA,MAC1H,wBAAwB,EAAE,mDAAmD,yBAAyB;AAAA,MACtG,uBAAuB,EAAE,8DAA8D,8BAAyB;AAAA,MAChH,sBAAsB,EAAE,wDAAwD,6BAA6B;AAAA,MAC7G,sBAAsB,EAAE,wDAAwD,aAAa;AAAA,MAC7F,gBAAgB,EAAE,kDAAkD,aAAa;AAAA,MACjF,mBAAmB,EAAE,kDAAkD,MAAM;AAAA,IAC/E;AAAA,EACF,IAAI,CAAC,QAAQ,CAAC,CAAC;AAEf,SACE;AAAA,IAAC;AAAA;AAAA,MACC,IAAI,uCAAuC,IAAI;AAAA,MAC/C,WAAU;AAAA,MAEV;AAAA,6BAAC,SAAI,WAAU,gCACb;AAAA,8BAAC,QAAG,WAAU,uBAAuB,iBAAM;AAAA,UAC3C,oBAAC,OAAE,WAAU,iCAAiC,uBAAY;AAAA,WAC5D;AAAA,QACA,oBAAC,SAAI,WAAU,qBACb;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA;AAAA,YACA,WAAS;AAAA,YACT,UAAU;AAAA,YACV,QAAQ;AAAA,YACR,UAAU;AAAA,YACV,WAAW;AAAA,YACX,cAAc;AAAA;AAAA,QAChB,GACF;AAAA,QACA,oBAAC,UAAO,MAAM,WAAW,MAAM,cAAc,CAAC,SAAS;AAAE,cAAI,CAAC,KAAM,aAAY;AAAA,QAAE,GAChF,+BAAC,iBAAc,WAAU,YACvB;AAAA,8BAAC,gBACC,8BAAC,eAAa,2BAAiB,OAAM,GACvC;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAM,QAAQ,SAAS,SAAS,SAAS;AAAA,cACzC,eAAe;AAAA,cACf,UAAU;AAAA,cACV,UAAU;AAAA,cACV;AAAA,cACA,cAAc;AAAA,cACd,iBAAiB;AAAA;AAAA,UACnB;AAAA,WACF,GACF;AAAA,QACC;AAAA;AAAA;AAAA,EACH;AAEJ;",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport dynamic from 'next/dynamic'\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport {\n getCustomerDictionarySettingsSectionId,\n type CustomerDictionaryKind,\n} from '../lib/dictionaries'\nimport { ICON_SUGGESTIONS } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\nimport {\n DictionaryForm,\n type DictionaryFormValues,\n} from '@open-mercato/core/modules/dictionaries/components/DictionaryForm'\nimport {\n DictionaryTable,\n type DictionaryTableEntry,\n} from '@open-mercato/core/modules/dictionaries/components/DictionaryTable'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('customers')\nconst ActivityTypeEditor = dynamic(() => import('./ActivityTypeEditor').then((module) => module.ActivityTypeEditor), { ssr: false })\n\ntype SectionDefinition = {\n kind: CustomerDictionaryKind\n title: string\n description: string\n}\n\ntype DictionaryDeleteError = Error & {\n code?: string\n usageCount?: number\n}\n\ntype DialogState =\n | { mode: 'create' }\n | { mode: 'edit'; entry: DictionaryTableEntry }\n\nconst DEFAULT_FORM_VALUES: DictionaryFormValues = {\n value: '',\n label: '',\n color: null,\n icon: null,\n}\n\nconst DICTIONARY_HASH_SCROLL_RETRY_DELAYS_MS = [100, 350, 1000] as const\n\nfunction getCurrentDictionaryHashTarget(): HTMLElement | null {\n if (typeof window === 'undefined') return null\n const hash = window.location.hash\n if (!hash.startsWith('#customer-dictionary-')) return null\n try {\n return document.getElementById(decodeURIComponent(hash.slice(1)))\n } catch {\n return document.getElementById(hash.slice(1))\n }\n}\n\nexport default function DictionarySettings() {\n const t = useT()\n\n const sections = React.useMemo<SectionDefinition[]>(() => [\n {\n kind: 'person-company-roles',\n title: t('customers.config.dictionaries.sections.personCompanyRoles.title', 'Role types'),\n description: t('customers.config.dictionaries.sections.personCompanyRoles.description', 'Manage the ownership roles available in People tab assignments.'),\n },\n {\n kind: 'statuses',\n title: t('customers.config.dictionaries.sections.statuses.title', 'Statuses'),\n description: t('customers.config.dictionaries.sections.statuses.description', 'Define the statuses available for customer records.'),\n },\n {\n kind: 'deal-statuses',\n title: t('customers.config.dictionaries.sections.dealStatuses.title', 'Deal statuses'),\n description: t('customers.config.dictionaries.sections.dealStatuses.description', 'Manage the statuses available for deals.'),\n },\n {\n kind: 'job-titles',\n title: t('customers.config.dictionaries.sections.jobTitles.title', 'Job titles'),\n description: t('customers.config.dictionaries.sections.jobTitles.description', 'Configure job titles with their appearance.'),\n },\n {\n kind: 'sources',\n title: t('customers.config.dictionaries.sections.sources.title', 'Sources'),\n description: t('customers.config.dictionaries.sections.sources.description', 'Capture how customers were acquired.'),\n },\n {\n kind: 'industries',\n title: t('customers.config.dictionaries.sections.industries.title', 'Industries'),\n description: t('customers.config.dictionaries.sections.industries.description', 'Manage the industries used by companies.'),\n },\n {\n kind: 'lifecycle-stages',\n title: t('customers.config.dictionaries.sections.lifecycle.title', 'Lifecycle stages'),\n description: t('customers.config.dictionaries.sections.lifecycle.description', 'Configure lifecycle stages to track customer progress.'),\n },\n {\n kind: 'activity-types',\n title: t('customers.config.dictionaries.sections.activityTypes.title', 'Activity types'),\n description: t('customers.config.dictionaries.sections.activityTypes.description', 'Define the activity types used for customer interactions.'),\n },\n {\n kind: 'interaction-statuses',\n title: t('customers.config.dictionaries.sections.interactionStatuses.title', 'Interaction statuses'),\n description: t('customers.config.dictionaries.sections.interactionStatuses.description', 'Manage the statuses available for tasks and logged interactions.'),\n },\n {\n kind: 'address-types',\n title: t('customers.config.dictionaries.sections.addressTypes.title', 'Address types'),\n description: t('customers.config.dictionaries.sections.addressTypes.description', 'Define the available address types.'),\n },\n ], [t])\n\n React.useEffect(() => {\n const timeoutIds: number[] = []\n let frameId: number | null = null\n\n const clearScheduledScrolls = () => {\n if (frameId !== null) {\n window.cancelAnimationFrame(frameId)\n frameId = null\n }\n while (timeoutIds.length > 0) {\n const timeoutId = timeoutIds.pop()\n if (timeoutId !== undefined) window.clearTimeout(timeoutId)\n }\n }\n\n const scrollToHashTarget = () => {\n getCurrentDictionaryHashTarget()?.scrollIntoView({ block: 'start' })\n }\n\n const scheduleHashScroll = () => {\n clearScheduledScrolls()\n if (!window.location.hash.startsWith('#customer-dictionary-')) return\n scrollToHashTarget()\n if (typeof window.requestAnimationFrame === 'function') {\n frameId = window.requestAnimationFrame(scrollToHashTarget)\n }\n DICTIONARY_HASH_SCROLL_RETRY_DELAYS_MS.forEach((delay) => {\n timeoutIds.push(window.setTimeout(scrollToHashTarget, delay))\n })\n }\n\n scheduleHashScroll()\n window.addEventListener('hashchange', scheduleHashScroll)\n return () => {\n window.removeEventListener('hashchange', scheduleHashScroll)\n clearScheduledScrolls()\n }\n }, [])\n\n return (\n <div className=\"space-y-8\">\n <header className=\"space-y-2\">\n <h1 className=\"text-2xl font-semibold\">\n {t('customers.config.dictionaries.title', 'Customers dictionaries')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {t('customers.config.dictionaries.description', 'Manage the dictionaries used by the customers module.')}\n </p>\n </header>\n\n <div className=\"space-y-6\">\n {sections.map((section) => section.kind === 'activity-types'\n ? <ActivityTypeEditor key={section.kind} title={section.title} description={section.description} />\n : <CustomerDictionarySection key={section.kind} {...section} />)}\n </div>\n </div>\n )\n}\n\ntype CustomerDictionarySectionProps = SectionDefinition\n\nfunction CustomerDictionarySection({ kind, title, description }: CustomerDictionarySectionProps) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const scopeVersion = useOrganizationScopeVersion()\n const [entries, setEntries] = React.useState<DictionaryTableEntry[]>([])\n const [loading, setLoading] = React.useState<boolean>(true)\n const [dialog, setDialog] = React.useState<DialogState | null>(null)\n const [submitting, setSubmitting] = React.useState(false)\n\n const saveContextId = `customers-dictionary-${kind}`\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: saveContextId,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const mutationContext = React.useMemo(\n () => ({\n formId: saveContextId,\n resourceKind: 'customers.dictionary',\n retryLastMutation,\n }),\n [retryLastMutation, saveContextId],\n )\n\n const inheritedActionBlocked = t('customers.config.dictionaries.inherited.blocked', 'Inherited entries can only be edited from the parent organization.')\n const inheritedTooltip = t('customers.config.dictionaries.inherited.tooltip', 'Managed in parent organization')\n const inheritedLabel = t('customers.config.dictionaries.inherited.label', 'Inherited')\n const errorLoad = t('customers.config.dictionaries.error.load', 'Failed to load dictionary entries.')\n const errorSave = t('customers.config.dictionaries.error.save', 'Failed to save dictionary entry.')\n const errorDelete = t('customers.config.dictionaries.error.delete', 'Failed to delete dictionary entry.')\n const successSave = t('customers.config.dictionaries.success.save', 'Dictionary entry saved.')\n const successDelete = t('customers.config.dictionaries.success.delete', 'Dictionary entry deleted.')\n const deleteConfirmTemplate = t('customers.config.dictionaries.deleteConfirm', 'Delete \"{{value}}\"?')\n const roleTypeDeleteBlockedTitle = t('customers.config.dictionaries.roleTypes.deleteBlocked.title', 'Role type is in use')\n const roleTypeDeleteBlockedText = t(\n 'customers.config.dictionaries.roleTypes.deleteBlocked.text',\n 'This role type is assigned to {{count}} records. Remove or replace those assignments before deleting it.',\n )\n const usageCountLabel = t('customers.config.dictionaries.roleTypes.usageCount', 'In use on {{count}} records')\n const searchPlaceholder = t('customers.config.dictionaries.searchPlaceholder', 'Search entries\u2026')\n\n const formatCountMessage = React.useCallback((template: string, count: number) => {\n return template.replace('{{count}}', String(count))\n }, [])\n\n const loadEntries = React.useCallback(async () => {\n setLoading(true)\n try {\n const data = await readApiResultOrThrow<{ items?: unknown[] }>(\n `/api/customers/dictionaries/${kind}`,\n undefined,\n { errorMessage: errorLoad },\n )\n if (!Array.isArray(data?.items)) throw new Error(errorLoad)\n const mapped: DictionaryTableEntry[] = data.items.map((item: any) => ({\n id: String(item.id),\n value: String(item.value ?? ''),\n label: typeof item.label === 'string' ? item.label : '',\n color: typeof item.color === 'string' ? item.color : null,\n icon: typeof item.icon === 'string' ? item.icon : null,\n usageCount: typeof item.usageCount === 'number' ? item.usageCount : undefined,\n organizationId: typeof item.organizationId === 'string' ? item.organizationId : null,\n tenantId: typeof item.tenantId === 'string' ? item.tenantId : null,\n isInherited: item.isInherited === true,\n createdAt: typeof item.createdAt === 'string' ? item.createdAt : null,\n updatedAt: typeof item.updatedAt === 'string' ? item.updatedAt : null,\n }))\n setEntries(mapped)\n } catch (err) {\n logger.error('customers.dictionaries.list failed', { err })\n flash(errorLoad, 'error')\n } finally {\n setLoading(false)\n }\n }, [errorLoad, kind, scopeVersion])\n\n React.useEffect(() => {\n loadEntries().catch(() => {})\n }, [loadEntries])\n\n const closeDialog = React.useCallback(() => {\n setDialog(null)\n }, [])\n\n const handleCreate = React.useCallback(() => {\n setDialog({ mode: 'create' })\n }, [])\n\n const handleEdit = React.useCallback((entry: DictionaryTableEntry) => {\n if (entry.isInherited) {\n flash(inheritedActionBlocked, 'info')\n return\n }\n setDialog({ mode: 'edit', entry })\n }, [inheritedActionBlocked])\n\n const handleDelete = React.useCallback(async (entry: DictionaryTableEntry) => {\n if (entry.isInherited) {\n flash(inheritedActionBlocked, 'info')\n return\n }\n if (kind === 'person-company-roles' && (entry.usageCount ?? 0) > 0) {\n await confirm({\n title: roleTypeDeleteBlockedTitle,\n description: formatCountMessage(roleTypeDeleteBlockedText, entry.usageCount ?? 0),\n confirmText: false,\n cancelText: t('customers.config.dictionaries.dialog.cancel', 'Cancel'),\n })\n return\n }\n const message = deleteConfirmTemplate.replace('{{value}}', entry.label || entry.value)\n const confirmed = await confirm({\n title: message,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(entry.updatedAt), () =>\n apiCallOrThrow(\n `/api/customers/dictionaries/${kind}/${encodeURIComponent(entry.id)}`,\n { method: 'DELETE' },\n { errorMessage: errorDelete },\n ),\n ),\n context: mutationContext,\n mutationPayload: { action: 'delete', id: entry.id, kind },\n })\n flash(successDelete, 'success')\n await loadEntries()\n } catch (err) {\n logger.error('customers.dictionaries.delete failed', { err })\n if (kind === 'person-company-roles' && (err as DictionaryDeleteError)?.code === 'role_type_in_use') {\n const usageCount = Number((err as DictionaryDeleteError).usageCount ?? 0)\n await confirm({\n title: roleTypeDeleteBlockedTitle,\n description: formatCountMessage(roleTypeDeleteBlockedText, usageCount),\n confirmText: false,\n cancelText: t('customers.config.dictionaries.dialog.cancel', 'Cancel'),\n })\n return\n }\n const messageValue = err instanceof Error ? err.message : errorDelete\n flash(messageValue, 'error')\n }\n }, [confirm, deleteConfirmTemplate, errorDelete, formatCountMessage, inheritedActionBlocked, kind, loadEntries, mutationContext, roleTypeDeleteBlockedText, roleTypeDeleteBlockedTitle, runMutation, successDelete, t])\n\n const submitForm = React.useCallback(async (values: DictionaryFormValues) => {\n const payload = {\n value: values.value,\n label: values.label,\n color: values.color,\n icon: values.icon,\n }\n setSubmitting(true)\n try {\n if (!dialog || dialog.mode === 'create') {\n await runMutation({\n operation: () =>\n apiCallOrThrow(\n `/api/customers/dictionaries/${kind}`,\n {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(payload),\n },\n { errorMessage: errorSave },\n ),\n context: mutationContext,\n mutationPayload: { action: 'create', kind, ...payload },\n })\n flash(successSave, 'success')\n } else if (dialog.mode === 'edit') {\n const target = dialog.entry\n if (target.isInherited) {\n flash(inheritedActionBlocked, 'info')\n return\n }\n const body: Record<string, unknown> = {}\n if (values.value !== target.value) body.value = values.value\n if (values.label !== target.label) body.label = values.label\n const nextColor = values.color ?? null\n if (nextColor !== (target.color ?? null)) body.color = nextColor\n const nextIcon = values.icon ?? null\n if (nextIcon !== (target.icon ?? null)) body.icon = nextIcon\n if (Object.keys(body).length === 0) {\n closeDialog()\n return\n }\n await runMutation({\n operation: () =>\n withScopedApiRequestHeaders(buildOptimisticLockHeader(target.updatedAt), () =>\n apiCallOrThrow(\n `/api/customers/dictionaries/${kind}/${encodeURIComponent(target.id)}`,\n {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n },\n { errorMessage: errorSave },\n ),\n ),\n context: mutationContext,\n mutationPayload: { action: 'update', id: target.id, kind, ...body },\n })\n flash(successSave, 'success')\n }\n closeDialog()\n await loadEntries()\n } catch (err) {\n logger.error('customers.dictionaries.submit failed', { err })\n throw err instanceof Error ? err : new Error(errorSave)\n } finally {\n setSubmitting(false)\n }\n }, [closeDialog, dialog, errorSave, inheritedActionBlocked, kind, loadEntries, mutationContext, runMutation, successSave])\n\n const currentValues = React.useMemo<DictionaryFormValues>(() => {\n if (dialog && dialog.mode === 'edit') {\n return {\n value: dialog.entry.value,\n label: dialog.entry.label,\n color: dialog.entry.color,\n icon: dialog.entry.icon,\n }\n }\n return DEFAULT_FORM_VALUES\n }, [dialog])\n\n const tableTranslations = React.useMemo(() => ({\n title,\n valueColumn: t('customers.config.dictionaries.columns.value', 'Value'),\n labelColumn: t('customers.config.dictionaries.columns.label', 'Label'),\n appearanceColumn: t('customers.config.dictionaries.columns.appearance', 'Appearance'),\n addLabel: t('customers.config.dictionaries.actions.add', 'Add entry'),\n editLabel: t('customers.config.dictionaries.actions.edit', 'Edit'),\n deleteLabel: t('customers.config.dictionaries.actions.delete', 'Delete'),\n refreshLabel: t('customers.config.dictionaries.actions.refresh', 'Refresh'),\n inheritedLabel,\n inheritedTooltip,\n emptyLabel: t('customers.config.dictionaries.empty', 'No entries yet.'),\n usageCountLabel: kind === 'person-company-roles' ? usageCountLabel : undefined,\n searchPlaceholder,\n }), [inheritedLabel, inheritedTooltip, kind, searchPlaceholder, t, title, usageCountLabel])\n\n const formTranslations = React.useMemo(() => ({\n title: dialog?.mode === 'edit'\n ? t('customers.config.dictionaries.dialog.editTitle', 'Edit entry')\n : t('customers.config.dictionaries.dialog.addTitle', 'Add entry'),\n valueLabel: t('customers.config.dictionaries.dialog.valueLabel', 'Value'),\n labelLabel: t('customers.config.dictionaries.dialog.labelLabel', 'Label'),\n saveLabel: t('customers.config.dictionaries.dialog.save', 'Save'),\n cancelLabel: t('customers.config.dictionaries.dialog.cancel', 'Cancel'),\n appearance: {\n colorLabel: t('customers.config.dictionaries.dialog.colorLabel', 'Color'),\n colorHelp: t('customers.config.dictionaries.dialog.colorHelp', 'Pick a highlight color for this entry.'),\n colorClearLabel: t('customers.config.dictionaries.dialog.colorClear', 'Remove color'),\n iconLabel: t('customers.config.dictionaries.dialog.iconLabel', 'Icon'),\n iconPlaceholder: t('customers.config.dictionaries.dialog.iconPlaceholder', 'Type an emoji or pick one of the suggestions.'),\n iconPickerTriggerLabel: t('customers.config.dictionaries.dialog.iconBrowse', 'Browse icons and emojis'),\n iconSearchPlaceholder: t('customers.config.dictionaries.dialog.iconSearchPlaceholder', 'Search icons or emojis\u2026'),\n iconSearchEmptyLabel: t('customers.config.dictionaries.dialog.iconSearchEmpty', 'No icons match your search.'),\n iconSuggestionsLabel: t('customers.config.dictionaries.dialog.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('customers.config.dictionaries.dialog.iconClear', 'Remove icon'),\n previewEmptyLabel: t('customers.config.dictionaries.appearance.empty', 'None'),\n },\n }), [dialog, t])\n\n return (\n <section\n id={getCustomerDictionarySettingsSectionId(kind)}\n className=\"scroll-mt-24 rounded border bg-card text-card-foreground shadow-sm\"\n >\n <div className=\"border-b px-6 py-4 space-y-1\">\n <h2 className=\"text-lg font-medium\">{title}</h2>\n <p className=\"text-sm text-muted-foreground\">{description}</p>\n </div>\n <div className=\"px-2 py-4 sm:px-4\">\n <DictionaryTable\n entries={entries}\n loading={loading}\n canManage\n onCreate={handleCreate}\n onEdit={handleEdit}\n onDelete={handleDelete}\n onRefresh={loadEntries}\n translations={tableTranslations}\n />\n </div>\n <Dialog open={dialog !== null} onOpenChange={(open) => { if (!open) closeDialog() }}>\n <DialogContent className=\"max-w-lg\">\n <DialogHeader>\n <DialogTitle>{formTranslations.title}</DialogTitle>\n </DialogHeader>\n <DictionaryForm\n mode={dialog?.mode === 'edit' ? 'edit' : 'create'}\n initialValues={currentValues}\n onSubmit={submitForm}\n onCancel={closeDialog}\n submitting={submitting}\n translations={formTranslations}\n iconSuggestions={ICON_SUGGESTIONS}\n />\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </section>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|