@open-mercato/core 0.6.6-develop.6205.1.109e4b6a84 → 0.6.6-develop.6228.1.cf483c68b5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/currencies/backend/currencies/[id]/page.js +33 -8
- package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/page.js +63 -27
- package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/page.js +35 -14
- package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js +104 -41
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +57 -8
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +5 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +30 -14
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +29 -19
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +166 -64
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
- package/dist/modules/customers/components/AddressFormatSettings.js +31 -16
- package/dist/modules/customers/components/AddressFormatSettings.js.map +2 -2
- package/dist/modules/customers/components/DictionarySettings.js +57 -31
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/PipelineSettings.js +156 -76
- package/dist/modules/customers/components/PipelineSettings.js.map +2 -2
- package/dist/modules/customers/components/calendar/AgendaList.js +10 -4
- package/dist/modules/customers/components/calendar/AgendaList.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +18 -2
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
- package/dist/modules/customers/components/calendar/MonthGrid.js +5 -2
- package/dist/modules/customers/components/calendar/MonthGrid.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +6 -0
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/RelatedToField.js +2 -0
- package/dist/modules/customers/components/calendar/editor/RelatedToField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +4 -1
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
- package/dist/modules/customers/lib/calendar/labels.js +33 -0
- package/dist/modules/customers/lib/calendar/labels.js.map +7 -0
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +32 -11
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
- package/dist/modules/data_sync/api/mappings/[id]/route.js +59 -0
- package/dist/modules/data_sync/api/mappings/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/mappings/route.js +44 -0
- package/dist/modules/data_sync/api/mappings/route.js.map +2 -2
- package/dist/modules/data_sync/api/run.js +31 -0
- package/dist/modules/data_sync/api/run.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/cancel.js +31 -0
- package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/retry.js +31 -0
- package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/[id]/route.js +59 -3
- package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/route.js +33 -4
- package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
- package/dist/modules/dictionaries/fields/dictionary.js +29 -4
- package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
- package/dist/modules/directory/api/organization-branding/route.js +54 -2
- package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
- package/dist/modules/directory/backend/directory/branding/page.js +15 -9
- package/dist/modules/directory/backend/directory/branding/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/page.js +28 -9
- package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/tenants/page.js +29 -13
- package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
- package/dist/modules/entities/api/definitions.batch.js +15 -0
- package/dist/modules/entities/api/definitions.batch.js.map +2 -2
- package/dist/modules/entities/api/definitions.js +26 -0
- package/dist/modules/entities/api/definitions.js.map +2 -2
- package/dist/modules/entities/api/definitions.mutation-guard.js +57 -0
- package/dist/modules/entities/api/definitions.mutation-guard.js.map +7 -0
- package/dist/modules/entities/api/definitions.restore.js +15 -0
- package/dist/modules/entities/api/definitions.restore.js.map +2 -2
- package/dist/modules/entities/api/entities.js +31 -3
- package/dist/modules/entities/api/entities.js.map +2 -2
- package/dist/modules/entities/api/records.js +18 -0
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +28 -10
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/feature_toggles/api/overrides/route.js +38 -1
- package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/emails/[id]/route.js +30 -0
- package/dist/modules/inbox_ops/api/emails/[id]/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js +30 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js +31 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js +31 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js.map +2 -2
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js +68 -39
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js.map +2 -2
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +14 -4
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +2 -2
- package/dist/modules/payment_gateways/api/cancel/route.js +37 -0
- package/dist/modules/payment_gateways/api/cancel/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/capture/route.js +37 -0
- package/dist/modules/payment_gateways/api/capture/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/guards.js +31 -0
- package/dist/modules/payment_gateways/api/guards.js.map +7 -0
- package/dist/modules/payment_gateways/api/refund/route.js +37 -0
- package/dist/modules/payment_gateways/api/refund/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/sessions/route.js +37 -0
- package/dist/modules/payment_gateways/api/sessions/route.js.map +2 -2
- package/dist/modules/planner/api/availability-date-specific.js +11 -1
- package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js +20 -3
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
- package/dist/modules/planner/commands/availability-date-specific.js +16 -0
- package/dist/modules/planner/commands/availability-date-specific.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +109 -40
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/query_index/api/purge.js +35 -3
- package/dist/modules/query_index/api/purge.js.map +2 -2
- package/dist/modules/query_index/api/reindex.js +41 -3
- package/dist/modules/query_index/api/reindex.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js +57 -24
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/shipping_carriers/api/shipments/route.js +31 -0
- package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
- package/dist/modules/shipping_carriers/api/tracking/refresh/route.js +55 -0
- package/dist/modules/shipping_carriers/api/tracking/refresh/route.js.map +7 -0
- package/dist/modules/shipping_carriers/data/validators.js +6 -1
- package/dist/modules/shipping_carriers/data/validators.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js +28 -8
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipping-service.js +37 -7
- package/dist/modules/shipping_carriers/lib/shipping-service.js.map +2 -2
- package/dist/modules/shipping_carriers/workers/status-poller.js +1 -1
- package/dist/modules/shipping_carriers/workers/status-poller.js.map +2 -2
- package/dist/modules/translations/components/TranslationManager.js +49 -20
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/currencies/backend/currencies/[id]/page.tsx +40 -10
- package/src/modules/currencies/backend/currencies/page.tsx +68 -29
- package/src/modules/currencies/backend/exchange-rates/page.tsx +40 -15
- package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +110 -41
- package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +69 -7
- package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +16 -8
- package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +32 -14
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +32 -20
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +171 -64
- package/src/modules/customers/components/AddressFormatSettings.tsx +39 -19
- package/src/modules/customers/components/DictionarySettings.tsx +63 -29
- package/src/modules/customers/components/PipelineSettings.tsx +165 -80
- package/src/modules/customers/components/calendar/AgendaList.tsx +13 -8
- package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +16 -2
- package/src/modules/customers/components/calendar/MonthGrid.tsx +5 -2
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +6 -0
- package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +2 -0
- package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +7 -0
- package/src/modules/customers/i18n/de.json +11 -7
- package/src/modules/customers/i18n/en.json +6 -2
- package/src/modules/customers/i18n/es.json +11 -7
- package/src/modules/customers/i18n/pl.json +11 -7
- package/src/modules/customers/lib/calendar/labels.ts +42 -0
- package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +39 -11
- package/src/modules/data_sync/api/mappings/[id]/route.ts +63 -0
- package/src/modules/data_sync/api/mappings/route.ts +48 -0
- package/src/modules/data_sync/api/run.ts +33 -0
- package/src/modules/data_sync/api/runs/[id]/cancel.ts +34 -0
- package/src/modules/data_sync/api/runs/[id]/retry.ts +33 -0
- package/src/modules/data_sync/api/schedules/[id]/route.ts +64 -2
- package/src/modules/data_sync/api/schedules/route.ts +36 -4
- package/src/modules/dictionaries/fields/dictionary.tsx +31 -8
- package/src/modules/dictionaries/i18n/de.json +1 -0
- package/src/modules/dictionaries/i18n/en.json +1 -0
- package/src/modules/dictionaries/i18n/es.json +1 -0
- package/src/modules/dictionaries/i18n/pl.json +1 -0
- package/src/modules/directory/api/organization-branding/route.ts +61 -0
- package/src/modules/directory/backend/directory/branding/page.tsx +16 -10
- package/src/modules/directory/backend/directory/organizations/page.tsx +35 -8
- package/src/modules/directory/backend/directory/tenants/page.tsx +37 -13
- package/src/modules/entities/api/definitions.batch.ts +17 -0
- package/src/modules/entities/api/definitions.mutation-guard.ts +80 -0
- package/src/modules/entities/api/definitions.restore.ts +17 -0
- package/src/modules/entities/api/definitions.ts +30 -0
- package/src/modules/entities/api/entities.ts +35 -3
- package/src/modules/entities/api/records.ts +20 -0
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +33 -10
- package/src/modules/feature_toggles/api/overrides/route.ts +44 -1
- package/src/modules/inbox_ops/api/emails/[id]/route.ts +32 -0
- package/src/modules/inbox_ops/api/proposals/[id]/accept-all/route.ts +32 -0
- package/src/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.ts +33 -0
- package/src/modules/inbox_ops/api/proposals/[id]/categorize/route.ts +33 -0
- package/src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts +80 -42
- package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +21 -4
- package/src/modules/payment_gateways/api/cancel/route.ts +39 -0
- package/src/modules/payment_gateways/api/capture/route.ts +39 -0
- package/src/modules/payment_gateways/api/guards.ts +59 -0
- package/src/modules/payment_gateways/api/refund/route.ts +39 -0
- package/src/modules/payment_gateways/api/sessions/route.ts +40 -0
- package/src/modules/planner/api/availability-date-specific.ts +10 -0
- package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +26 -5
- package/src/modules/planner/commands/availability-date-specific.ts +24 -0
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +122 -41
- package/src/modules/query_index/api/purge.ts +37 -3
- package/src/modules/query_index/api/reindex.ts +43 -3
- package/src/modules/query_index/components/QueryIndexesTable.tsx +66 -24
- package/src/modules/shipping_carriers/api/shipments/route.ts +31 -0
- package/src/modules/shipping_carriers/api/tracking/refresh/route.ts +53 -0
- package/src/modules/shipping_carriers/data/validators.ts +5 -0
- package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.ts +29 -8
- package/src/modules/shipping_carriers/lib/shipping-service.ts +43 -7
- package/src/modules/shipping_carriers/workers/status-poller.ts +1 -1
- package/src/modules/translations/components/TranslationManager.tsx +65 -21
|
@@ -16,6 +16,7 @@ import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
|
16
16
|
import { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
|
|
17
17
|
import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
|
|
18
18
|
import { raiseCrudError } from "@open-mercato/ui/backend/utils/serverErrors";
|
|
19
|
+
import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
|
|
19
20
|
import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
|
|
20
21
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
21
22
|
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
@@ -69,6 +70,26 @@ function PipelineSettings() {
|
|
|
69
70
|
const [stageDialog, setStageDialog] = React.useState(null);
|
|
70
71
|
const [stageForm, setStageForm] = React.useState({ label: "", color: null, icon: null });
|
|
71
72
|
const [submittingStage, setSubmittingStage] = React.useState(false);
|
|
73
|
+
const { runMutation, retryLastMutation } = useGuardedMutation({
|
|
74
|
+
contextId: "customers-pipeline-settings",
|
|
75
|
+
blockedMessage: t("ui.forms.flash.saveBlocked", "Save blocked by validation")
|
|
76
|
+
});
|
|
77
|
+
const pipelineMutationContext = React.useMemo(
|
|
78
|
+
() => ({
|
|
79
|
+
formId: "customers-pipeline-settings",
|
|
80
|
+
resourceKind: "customers.pipeline",
|
|
81
|
+
retryLastMutation
|
|
82
|
+
}),
|
|
83
|
+
[retryLastMutation]
|
|
84
|
+
);
|
|
85
|
+
const stageMutationContext = React.useMemo(
|
|
86
|
+
() => ({
|
|
87
|
+
formId: "customers-pipeline-settings",
|
|
88
|
+
resourceKind: "customers.pipeline_stage",
|
|
89
|
+
retryLastMutation
|
|
90
|
+
}),
|
|
91
|
+
[retryLastMutation]
|
|
92
|
+
);
|
|
72
93
|
const loadPipelines = React.useCallback(async () => {
|
|
73
94
|
setLoadingPipelines(true);
|
|
74
95
|
try {
|
|
@@ -124,37 +145,49 @@ function PipelineSettings() {
|
|
|
124
145
|
setSubmittingPipeline(true);
|
|
125
146
|
try {
|
|
126
147
|
if (pipelineDialog?.mode === "create") {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
148
|
+
await runMutation({
|
|
149
|
+
operation: async () => {
|
|
150
|
+
const res = await apiCall("/api/customers/pipelines", {
|
|
151
|
+
method: "POST",
|
|
152
|
+
headers: { "content-type": "application/json" },
|
|
153
|
+
body: JSON.stringify({ name: pipelineForm.name.trim(), isDefault: pipelineForm.isDefault })
|
|
154
|
+
});
|
|
155
|
+
if (!res.ok) {
|
|
156
|
+
await raiseCrudError(res.response, t("customers.pipelines.errors.createFailed", "Failed to create pipeline"));
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
context: pipelineMutationContext,
|
|
160
|
+
mutationPayload: { action: "create", name: pipelineForm.name.trim(), isDefault: pipelineForm.isDefault }
|
|
131
161
|
});
|
|
132
|
-
if (!res.ok) {
|
|
133
|
-
await raiseCrudError(res.response, t("customers.pipelines.errors.createFailed", "Failed to create pipeline"));
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
162
|
flash(t("customers.pipelines.flash.created", "Pipeline created"), "success");
|
|
137
163
|
} else if (pipelineDialog?.mode === "edit") {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
164
|
+
await runMutation({
|
|
165
|
+
operation: async () => {
|
|
166
|
+
const res = await withScopedApiRequestHeaders(
|
|
167
|
+
buildOptimisticLockHeader(pipelineDialog.entry.updatedAt),
|
|
168
|
+
() => apiCall("/api/customers/pipelines", {
|
|
169
|
+
method: "PUT",
|
|
170
|
+
headers: { "content-type": "application/json" },
|
|
171
|
+
body: JSON.stringify({ id: pipelineDialog.entry.id, name: pipelineForm.name.trim(), isDefault: pipelineForm.isDefault })
|
|
172
|
+
})
|
|
173
|
+
);
|
|
174
|
+
if (!res.ok) {
|
|
175
|
+
await raiseCrudError(res.response, t("customers.pipelines.errors.updateFailed", "Failed to update pipeline"));
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
context: pipelineMutationContext,
|
|
179
|
+
mutationPayload: { action: "update", id: pipelineDialog.entry.id, name: pipelineForm.name.trim(), isDefault: pipelineForm.isDefault }
|
|
180
|
+
});
|
|
150
181
|
flash(t("customers.pipelines.flash.updated", "Pipeline updated"), "success");
|
|
151
182
|
}
|
|
152
183
|
setPipelineDialog(null);
|
|
153
184
|
await loadPipelines();
|
|
185
|
+
} catch {
|
|
186
|
+
return;
|
|
154
187
|
} finally {
|
|
155
188
|
setSubmittingPipeline(false);
|
|
156
189
|
}
|
|
157
|
-
}, [pipelineDialog, pipelineForm, loadPipelines, t]);
|
|
190
|
+
}, [pipelineDialog, pipelineForm, loadPipelines, pipelineMutationContext, runMutation, t]);
|
|
158
191
|
const handleDeletePipeline = React.useCallback(async (pipeline) => {
|
|
159
192
|
const confirmed = await confirm({
|
|
160
193
|
title: t("customers.pipelines.confirm.deleteTitle", "Delete pipeline"),
|
|
@@ -163,24 +196,35 @@ function PipelineSettings() {
|
|
|
163
196
|
variant: "destructive"
|
|
164
197
|
});
|
|
165
198
|
if (!confirmed) return;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
199
|
+
try {
|
|
200
|
+
await runMutation({
|
|
201
|
+
operation: async () => {
|
|
202
|
+
const res = await withScopedApiRequestHeaders(
|
|
203
|
+
buildOptimisticLockHeader(pipeline.updatedAt),
|
|
204
|
+
() => apiCall("/api/customers/pipelines", {
|
|
205
|
+
method: "DELETE",
|
|
206
|
+
headers: { "content-type": "application/json" },
|
|
207
|
+
body: JSON.stringify({ id: pipeline.id })
|
|
208
|
+
})
|
|
209
|
+
);
|
|
210
|
+
if (!res.ok) {
|
|
211
|
+
const body = res.result ?? {};
|
|
212
|
+
const msg = typeof body.error === "string" ? body.error : t("customers.pipelines.errors.deleteFailed", "Failed to delete pipeline");
|
|
213
|
+
throw new Error(msg);
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
context: pipelineMutationContext,
|
|
217
|
+
mutationPayload: { action: "delete", id: pipeline.id }
|
|
218
|
+
});
|
|
219
|
+
} catch (err) {
|
|
220
|
+
const msg = err instanceof Error && err.message ? err.message : t("customers.pipelines.errors.deleteFailed", "Failed to delete pipeline");
|
|
177
221
|
flash(msg, "error");
|
|
178
222
|
return;
|
|
179
223
|
}
|
|
180
224
|
flash(t("customers.pipelines.flash.deleted", "Pipeline deleted"), "success");
|
|
181
225
|
if (expandedPipelineId === pipeline.id) setExpandedPipelineId(null);
|
|
182
226
|
await loadPipelines();
|
|
183
|
-
}, [confirm, expandedPipelineId, loadPipelines, t]);
|
|
227
|
+
}, [confirm, expandedPipelineId, loadPipelines, pipelineMutationContext, runMutation, t]);
|
|
184
228
|
const openCreateStage = React.useCallback((pipelineId) => {
|
|
185
229
|
setStageForm({ label: "", color: null, icon: null });
|
|
186
230
|
setStageDialog({ mode: "create", pipelineId });
|
|
@@ -200,38 +244,52 @@ function PipelineSettings() {
|
|
|
200
244
|
if (stageForm.color) appearance.color = stageForm.color;
|
|
201
245
|
if (stageForm.icon) appearance.icon = stageForm.icon;
|
|
202
246
|
if (stageDialog?.mode === "create") {
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
247
|
+
const targetPipelineId = stageDialog.pipelineId;
|
|
248
|
+
await runMutation({
|
|
249
|
+
operation: async () => {
|
|
250
|
+
const res = await apiCall("/api/customers/pipeline-stages", {
|
|
251
|
+
method: "POST",
|
|
252
|
+
headers: { "content-type": "application/json" },
|
|
253
|
+
body: JSON.stringify({ pipelineId: targetPipelineId, label: stageForm.label.trim(), ...appearance })
|
|
254
|
+
});
|
|
255
|
+
if (!res.ok) {
|
|
256
|
+
await raiseCrudError(res.response, t("customers.pipelines.errors.stageCreateFailed", "Failed to create stage"));
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
context: stageMutationContext,
|
|
260
|
+
mutationPayload: { action: "create", pipelineId: targetPipelineId, label: stageForm.label.trim(), ...appearance }
|
|
207
261
|
});
|
|
208
|
-
if (!res.ok) {
|
|
209
|
-
await raiseCrudError(res.response, t("customers.pipelines.errors.stageCreateFailed", "Failed to create stage"));
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
262
|
flash(t("customers.pipelines.flash.stageCreated", "Stage created"), "success");
|
|
213
|
-
await loadStages(
|
|
263
|
+
await loadStages(targetPipelineId);
|
|
214
264
|
} else if (stageDialog?.mode === "edit") {
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
() =>
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
265
|
+
const targetStage = stageDialog.entry;
|
|
266
|
+
await runMutation({
|
|
267
|
+
operation: async () => {
|
|
268
|
+
const res = await withScopedApiRequestHeaders(
|
|
269
|
+
buildOptimisticLockHeader(targetStage.updatedAt),
|
|
270
|
+
() => apiCall("/api/customers/pipeline-stages", {
|
|
271
|
+
method: "PUT",
|
|
272
|
+
headers: { "content-type": "application/json" },
|
|
273
|
+
body: JSON.stringify({ id: targetStage.id, label: stageForm.label.trim(), ...appearance })
|
|
274
|
+
})
|
|
275
|
+
);
|
|
276
|
+
if (!res.ok) {
|
|
277
|
+
await raiseCrudError(res.response, t("customers.pipelines.errors.stageUpdateFailed", "Failed to update stage"));
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
context: stageMutationContext,
|
|
281
|
+
mutationPayload: { action: "update", id: targetStage.id, label: stageForm.label.trim(), ...appearance }
|
|
282
|
+
});
|
|
227
283
|
flash(t("customers.pipelines.flash.stageUpdated", "Stage updated"), "success");
|
|
228
|
-
await loadStages(
|
|
284
|
+
await loadStages(targetStage.pipelineId);
|
|
229
285
|
}
|
|
230
286
|
setStageDialog(null);
|
|
287
|
+
} catch {
|
|
288
|
+
return;
|
|
231
289
|
} finally {
|
|
232
290
|
setSubmittingStage(false);
|
|
233
291
|
}
|
|
234
|
-
}, [stageDialog, stageForm, loadStages, t]);
|
|
292
|
+
}, [stageDialog, stageForm, loadStages, runMutation, stageMutationContext, t]);
|
|
235
293
|
const handleDeleteStage = React.useCallback(async (stage) => {
|
|
236
294
|
const confirmed = await confirm({
|
|
237
295
|
title: t("customers.pipelines.confirm.stageDeleteTitle", "Delete stage"),
|
|
@@ -240,23 +298,34 @@ function PipelineSettings() {
|
|
|
240
298
|
variant: "destructive"
|
|
241
299
|
});
|
|
242
300
|
if (!confirmed) return;
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
301
|
+
try {
|
|
302
|
+
await runMutation({
|
|
303
|
+
operation: async () => {
|
|
304
|
+
const res = await withScopedApiRequestHeaders(
|
|
305
|
+
buildOptimisticLockHeader(stage.updatedAt),
|
|
306
|
+
() => apiCall("/api/customers/pipeline-stages", {
|
|
307
|
+
method: "DELETE",
|
|
308
|
+
headers: { "content-type": "application/json" },
|
|
309
|
+
body: JSON.stringify({ id: stage.id })
|
|
310
|
+
})
|
|
311
|
+
);
|
|
312
|
+
if (!res.ok) {
|
|
313
|
+
const body = res.result ?? {};
|
|
314
|
+
const msg = typeof body.error === "string" ? body.error : t("customers.pipelines.errors.stageDeleteFailed", "Failed to delete stage");
|
|
315
|
+
throw new Error(msg);
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
context: stageMutationContext,
|
|
319
|
+
mutationPayload: { action: "delete", id: stage.id }
|
|
320
|
+
});
|
|
321
|
+
} catch (err) {
|
|
322
|
+
const msg = err instanceof Error && err.message ? err.message : t("customers.pipelines.errors.stageDeleteFailed", "Failed to delete stage");
|
|
254
323
|
flash(msg, "error");
|
|
255
324
|
return;
|
|
256
325
|
}
|
|
257
326
|
flash(t("customers.pipelines.flash.stageDeleted", "Stage deleted"), "success");
|
|
258
327
|
await loadStages(stage.pipelineId);
|
|
259
|
-
}, [confirm, loadStages, t]);
|
|
328
|
+
}, [confirm, loadStages, runMutation, stageMutationContext, t]);
|
|
260
329
|
const handleMoveStage = React.useCallback(async (stage, direction) => {
|
|
261
330
|
const pipelineStages = stages[stage.pipelineId] ?? [];
|
|
262
331
|
const idx = pipelineStages.findIndex((s) => s.id === stage.id);
|
|
@@ -268,17 +337,28 @@ function PipelineSettings() {
|
|
|
268
337
|
reordered[idx] = reordered[swapIdx];
|
|
269
338
|
reordered[swapIdx] = temp;
|
|
270
339
|
const orderedStages = reordered.map((s, i) => ({ id: s.id, order: i }));
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
340
|
+
try {
|
|
341
|
+
await runMutation({
|
|
342
|
+
operation: async () => {
|
|
343
|
+
const res = await apiCall("/api/customers/pipeline-stages/reorder", {
|
|
344
|
+
method: "POST",
|
|
345
|
+
headers: { "content-type": "application/json" },
|
|
346
|
+
body: JSON.stringify({ stages: orderedStages })
|
|
347
|
+
});
|
|
348
|
+
if (!res.ok) {
|
|
349
|
+
throw new Error(t("customers.pipelines.errors.reorderFailed", "Failed to reorder stages"));
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
context: stageMutationContext,
|
|
353
|
+
mutationPayload: { action: "reorder", stages: orderedStages }
|
|
354
|
+
});
|
|
355
|
+
} catch (err) {
|
|
356
|
+
const msg = err instanceof Error && err.message ? err.message : t("customers.pipelines.errors.reorderFailed", "Failed to reorder stages");
|
|
357
|
+
flash(msg, "error");
|
|
278
358
|
return;
|
|
279
359
|
}
|
|
280
360
|
await loadStages(stage.pipelineId);
|
|
281
|
-
}, [stages, loadStages, t]);
|
|
361
|
+
}, [stages, loadStages, runMutation, stageMutationContext, t]);
|
|
282
362
|
const handleKeyDown = React.useCallback(
|
|
283
363
|
(handler) => (e) => {
|
|
284
364
|
if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/customers/components/PipelineSettings.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\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 { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from '@open-mercato/ui/primitives/accordion'\nimport {\n AppearanceSelector,\n type AppearanceSelectorLabels,\n} from '@open-mercato/core/modules/dictionaries/components/AppearanceSelector'\nimport {\n renderDictionaryColor,\n renderDictionaryIcon,\n} from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\n\ntype Pipeline = {\n id: string\n name: string\n isDefault: boolean\n organizationId: string\n tenantId: string\n updatedAt?: string | null\n}\n\ntype PipelineStage = {\n id: string\n pipelineId: string\n label: string\n order: number\n color: string | null\n icon: string | null\n updatedAt?: string | null\n}\n\ntype PipelineDialogState =\n | { mode: 'create' }\n | { mode: 'edit'; entry: Pipeline }\n\ntype StageDialogState =\n | { mode: 'create'; pipelineId: string }\n | { mode: 'edit'; entry: PipelineStage }\n\nfunction normalizePipeline(raw: Record<string, unknown>): Pipeline {\n return {\n id: typeof raw.id === 'string' ? raw.id : '',\n name: typeof raw.name === 'string' ? raw.name : '',\n isDefault: raw.isDefault === true || raw.is_default === true,\n organizationId: typeof raw.organizationId === 'string' ? raw.organizationId : (typeof raw.organization_id === 'string' ? raw.organization_id : ''),\n tenantId: typeof raw.tenantId === 'string' ? raw.tenantId : (typeof raw.tenant_id === 'string' ? raw.tenant_id : ''),\n updatedAt: typeof raw.updatedAt === 'string' ? raw.updatedAt : (typeof raw.updated_at === 'string' ? raw.updated_at : null),\n }\n}\n\nfunction normalizeStage(raw: Record<string, unknown>): PipelineStage {\n return {\n id: typeof raw.id === 'string' ? raw.id : '',\n pipelineId: typeof raw.pipelineId === 'string' ? raw.pipelineId : (typeof raw.pipeline_id === 'string' ? raw.pipeline_id : ''),\n label: typeof raw.label === 'string' ? raw.label : '',\n order: typeof raw.order === 'number' ? raw.order : 0,\n color: typeof raw.color === 'string' && raw.color.trim().length ? raw.color.trim() : null,\n icon: typeof raw.icon === 'string' && raw.icon.trim().length ? raw.icon.trim() : null,\n updatedAt: typeof raw.updatedAt === 'string' ? raw.updatedAt : (typeof raw.updated_at === 'string' ? raw.updated_at : null),\n }\n}\n\nexport default function PipelineSettings(): React.ReactElement {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const [pipelines, setPipelines] = React.useState<Pipeline[]>([])\n const [loadingPipelines, setLoadingPipelines] = React.useState(false)\n const [pipelineDialog, setPipelineDialog] = React.useState<PipelineDialogState | null>(null)\n const [pipelineForm, setPipelineForm] = React.useState({ name: '', isDefault: false })\n const [submittingPipeline, setSubmittingPipeline] = React.useState(false)\n\n const [expandedPipelineId, setExpandedPipelineId] = React.useState<string | null>(null)\n const [stages, setStages] = React.useState<Record<string, PipelineStage[]>>({})\n const [loadingStages, setLoadingStages] = React.useState<Record<string, boolean>>({})\n const [stageDialog, setStageDialog] = React.useState<StageDialogState | null>(null)\n const [stageForm, setStageForm] = React.useState({ label: '', color: null as string | null, icon: null as string | null })\n const [submittingStage, setSubmittingStage] = React.useState(false)\n\n const loadPipelines = React.useCallback(async () => {\n setLoadingPipelines(true)\n try {\n const data = await readApiResultOrThrow<{ items?: unknown[] }>(\n '/api/customers/pipelines',\n undefined,\n { errorMessage: t('customers.pipelines.errors.loadFailed', 'Failed to load pipelines'), fallback: { items: [] } },\n )\n const items = Array.isArray(data?.items) ? data.items : []\n setPipelines(items.map((item) => normalizePipeline(item as Record<string, unknown>)))\n } finally {\n setLoadingPipelines(false)\n }\n }, [t])\n\n const loadStages = React.useCallback(async (pipelineId: string) => {\n setLoadingStages((prev) => ({ ...prev, [pipelineId]: true }))\n try {\n const data = await readApiResultOrThrow<{ items?: unknown[] }>(\n `/api/customers/pipeline-stages?pipelineId=${encodeURIComponent(pipelineId)}`,\n undefined,\n { errorMessage: t('customers.pipelines.errors.stagesLoadFailed', 'Failed to load stages'), fallback: { items: [] } },\n )\n const items = Array.isArray(data?.items) ? data.items : []\n setStages((prev) => ({\n ...prev,\n [pipelineId]: items.map((item) => normalizeStage(item as Record<string, unknown>)),\n }))\n } finally {\n setLoadingStages((prev) => ({ ...prev, [pipelineId]: false }))\n }\n }, [t])\n\n React.useEffect(() => {\n void loadPipelines()\n }, [loadPipelines, scopeVersion])\n\n React.useEffect(() => {\n if (expandedPipelineId) {\n void loadStages(expandedPipelineId)\n }\n }, [expandedPipelineId, loadStages])\n\n const openCreatePipeline = React.useCallback(() => {\n setPipelineForm({ name: '', isDefault: false })\n setPipelineDialog({ mode: 'create' })\n }, [])\n\n const openEditPipeline = React.useCallback((pipeline: Pipeline) => {\n setPipelineForm({ name: pipeline.name, isDefault: pipeline.isDefault })\n setPipelineDialog({ mode: 'edit', entry: pipeline })\n }, [])\n\n const closePipelineDialog = React.useCallback(() => {\n setPipelineDialog(null)\n }, [])\n\n const handlePipelineSubmit = React.useCallback(async () => {\n if (!pipelineForm.name.trim()) return\n setSubmittingPipeline(true)\n try {\n if (pipelineDialog?.mode === 'create') {\n const res = await apiCall('/api/customers/pipelines', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ name: pipelineForm.name.trim(), isDefault: pipelineForm.isDefault }),\n })\n if (!res.ok) {\n await raiseCrudError(res.response, t('customers.pipelines.errors.createFailed', 'Failed to create pipeline'))\n return\n }\n flash(t('customers.pipelines.flash.created', 'Pipeline created'), 'success')\n } else if (pipelineDialog?.mode === 'edit') {\n const res = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(pipelineDialog.entry.updatedAt),\n () =>\n apiCall('/api/customers/pipelines', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: pipelineDialog.entry.id, name: pipelineForm.name.trim(), isDefault: pipelineForm.isDefault }),\n }),\n )\n if (!res.ok) {\n await raiseCrudError(res.response, t('customers.pipelines.errors.updateFailed', 'Failed to update pipeline'))\n return\n }\n flash(t('customers.pipelines.flash.updated', 'Pipeline updated'), 'success')\n }\n setPipelineDialog(null)\n await loadPipelines()\n } finally {\n setSubmittingPipeline(false)\n }\n }, [pipelineDialog, pipelineForm, loadPipelines, t])\n\n const handleDeletePipeline = React.useCallback(async (pipeline: Pipeline) => {\n const confirmed = await confirm({\n title: t('customers.pipelines.confirm.deleteTitle', 'Delete pipeline'),\n text: t('customers.pipelines.confirm.deleteDesc', 'Are you sure you want to delete this pipeline? This cannot be undone.'),\n confirmText: t('customers.pipelines.confirm.deleteConfirm', 'Delete'),\n variant: 'destructive',\n })\n if (!confirmed) return\n const res = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(pipeline.updatedAt),\n () =>\n apiCall('/api/customers/pipelines', {\n method: 'DELETE',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: pipeline.id }),\n }),\n )\n if (!res.ok) {\n const body = (res.result ?? {}) as Record<string, unknown>\n const msg = typeof body.error === 'string' ? body.error : t('customers.pipelines.errors.deleteFailed', 'Failed to delete pipeline')\n flash(msg, 'error')\n return\n }\n flash(t('customers.pipelines.flash.deleted', 'Pipeline deleted'), 'success')\n if (expandedPipelineId === pipeline.id) setExpandedPipelineId(null)\n await loadPipelines()\n }, [confirm, expandedPipelineId, loadPipelines, t])\n\n const openCreateStage = React.useCallback((pipelineId: string) => {\n setStageForm({ label: '', color: null, icon: null })\n setStageDialog({ mode: 'create', pipelineId })\n }, [])\n\n const openEditStage = React.useCallback((stage: PipelineStage) => {\n setStageForm({ label: stage.label, color: stage.color, icon: stage.icon })\n setStageDialog({ mode: 'edit', entry: stage })\n }, [])\n\n const closeStageDialog = React.useCallback(() => {\n setStageDialog(null)\n }, [])\n\n const handleStageSubmit = React.useCallback(async () => {\n if (!stageForm.label.trim()) return\n setSubmittingStage(true)\n try {\n const appearance: Record<string, unknown> = {}\n if (stageForm.color) appearance.color = stageForm.color\n if (stageForm.icon) appearance.icon = stageForm.icon\n\n if (stageDialog?.mode === 'create') {\n const res = await apiCall('/api/customers/pipeline-stages', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ pipelineId: stageDialog.pipelineId, label: stageForm.label.trim(), ...appearance }),\n })\n if (!res.ok) {\n await raiseCrudError(res.response, t('customers.pipelines.errors.stageCreateFailed', 'Failed to create stage'))\n return\n }\n flash(t('customers.pipelines.flash.stageCreated', 'Stage created'), 'success')\n await loadStages(stageDialog.pipelineId)\n } else if (stageDialog?.mode === 'edit') {\n const res = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(stageDialog.entry.updatedAt),\n () =>\n apiCall('/api/customers/pipeline-stages', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: stageDialog.entry.id, label: stageForm.label.trim(), ...appearance }),\n }),\n )\n if (!res.ok) {\n await raiseCrudError(res.response, t('customers.pipelines.errors.stageUpdateFailed', 'Failed to update stage'))\n return\n }\n flash(t('customers.pipelines.flash.stageUpdated', 'Stage updated'), 'success')\n await loadStages(stageDialog.entry.pipelineId)\n }\n setStageDialog(null)\n } finally {\n setSubmittingStage(false)\n }\n }, [stageDialog, stageForm, loadStages, t])\n\n const handleDeleteStage = React.useCallback(async (stage: PipelineStage) => {\n const confirmed = await confirm({\n title: t('customers.pipelines.confirm.stageDeleteTitle', 'Delete stage'),\n text: t('customers.pipelines.confirm.stageDeleteDesc', 'Are you sure you want to delete this stage?'),\n confirmText: t('customers.pipelines.confirm.stageDeleteConfirm', 'Delete'),\n variant: 'destructive',\n })\n if (!confirmed) return\n const res = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(stage.updatedAt),\n () =>\n apiCall('/api/customers/pipeline-stages', {\n method: 'DELETE',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: stage.id }),\n }),\n )\n if (!res.ok) {\n const body = (res.result ?? {}) as Record<string, unknown>\n const msg = typeof body.error === 'string' ? body.error : t('customers.pipelines.errors.stageDeleteFailed', 'Failed to delete stage')\n flash(msg, 'error')\n return\n }\n flash(t('customers.pipelines.flash.stageDeleted', 'Stage deleted'), 'success')\n await loadStages(stage.pipelineId)\n }, [confirm, loadStages, t])\n\n const handleMoveStage = React.useCallback(async (stage: PipelineStage, direction: 'up' | 'down') => {\n const pipelineStages = stages[stage.pipelineId] ?? []\n const idx = pipelineStages.findIndex((s) => s.id === stage.id)\n if (idx < 0) return\n const swapIdx = direction === 'up' ? idx - 1 : idx + 1\n if (swapIdx < 0 || swapIdx >= pipelineStages.length) return\n\n const reordered = [...pipelineStages]\n const temp = reordered[idx]\n reordered[idx] = reordered[swapIdx]\n reordered[swapIdx] = temp\n\n const orderedStages = reordered.map((s, i) => ({ id: s.id, order: i }))\n const res = await apiCall('/api/customers/pipeline-stages/reorder', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ stages: orderedStages }),\n })\n if (!res.ok) {\n flash(t('customers.pipelines.errors.reorderFailed', 'Failed to reorder stages'), 'error')\n return\n }\n await loadStages(stage.pipelineId)\n }, [stages, loadStages, t])\n\n const handleKeyDown = React.useCallback(\n (handler: () => void) => (e: React.KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n e.preventDefault()\n handler()\n }\n },\n [],\n )\n\n const appearanceLabels = React.useMemo<AppearanceSelectorLabels>(() => ({\n colorLabel: t('customers.pipelines.stageForm.color', 'Color'),\n colorClearLabel: t('customers.pipelines.stageForm.colorClear', 'Remove color'),\n iconLabel: t('customers.pipelines.stageForm.icon', 'Icon'),\n iconPlaceholder: t('customers.pipelines.stageForm.iconPlaceholder', 'e.g. lucide:star'),\n iconPickerTriggerLabel: t('customers.pipelines.stageForm.iconPicker', 'Pick icon'),\n iconSearchPlaceholder: t('customers.pipelines.stageForm.iconSearch', 'Search icons\u2026'),\n iconSearchEmptyLabel: t('customers.pipelines.stageForm.iconSearchEmpty', 'No icons found'),\n iconSuggestionsLabel: t('customers.pipelines.stageForm.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('customers.pipelines.stageForm.iconClear', 'Remove icon'),\n previewEmptyLabel: t('customers.pipelines.stageForm.previewEmpty', 'No appearance set'),\n }), [t])\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"text-base font-semibold\">{t('customers.pipelines.title', 'Sales Pipelines')}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {t('customers.pipelines.description', 'Manage sales pipelines and their stages.')}\n </p>\n </div>\n <Button size=\"sm\" onClick={openCreatePipeline}>\n {t('customers.pipelines.actions.create', 'Add pipeline')}\n </Button>\n </div>\n\n {loadingPipelines ? (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner className=\"h-4 w-4\" />\n {t('customers.pipelines.loading', 'Loading pipelines\u2026')}\n </div>\n ) : pipelines.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('customers.pipelines.empty', 'No pipelines yet. Create one to get started.')}\n </p>\n ) : (\n <Accordion\n type=\"single\"\n collapsible\n value={expandedPipelineId ?? ''}\n onValueChange={(next) => setExpandedPipelineId(next || null)}\n className=\"space-y-2\"\n >\n {pipelines.map((pipeline) => {\n const pipelineStages = stages[pipeline.id] ?? []\n const isLoadingStages = loadingStages[pipeline.id] ?? false\n\n return (\n <AccordionItem key={pipeline.id} value={pipeline.id}>\n <AccordionTrigger triggerIcon=\"chevron\">\n <span className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium\">{pipeline.name}</span>\n {pipeline.isDefault ? (\n <span className=\"rounded-full bg-primary/10 px-2 py-0.5 text-xs text-primary\">\n {t('customers.pipelines.defaultBadge', 'Default')}\n </span>\n ) : null}\n </span>\n </AccordionTrigger>\n <AccordionContent>\n <div className=\"mb-3 flex items-center justify-between\">\n <span className=\"text-xs font-medium text-muted-foreground uppercase tracking-wide\">\n {t('customers.pipelines.stages.title', 'Stages')}\n </span>\n <Button size=\"sm\" variant=\"outline\" onClick={() => openCreateStage(pipeline.id)}>\n {t('customers.pipelines.stages.add', 'Add stage')}\n </Button>\n </div>\n\n {isLoadingStages ? (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner className=\"h-3 w-3\" />\n {t('customers.pipelines.stages.loading', 'Loading\u2026')}\n </div>\n ) : pipelineStages.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('customers.pipelines.stages.empty', 'No stages yet.')}\n </p>\n ) : (\n <div className=\"divide-y divide-border rounded-md border bg-background\">\n {pipelineStages.map((stage, idx) => (\n <div key={stage.id} className=\"flex items-center justify-between gap-3 px-3 py-2\">\n <div className=\"flex items-center gap-2\">\n <span className=\"w-5 text-center text-xs text-muted-foreground\">{idx + 1}</span>\n {stage.color ? renderDictionaryColor(stage.color, 'h-3 w-3 rounded-full') : null}\n {stage.icon ? renderDictionaryIcon(stage.icon, 'h-4 w-4') : null}\n <span className=\"text-sm\">{stage.label}</span>\n </div>\n <div className=\"flex items-center gap-1\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7\"\n disabled={idx === 0}\n onClick={() => void handleMoveStage(stage, 'up')}\n title={t('customers.pipelines.stages.moveUp', 'Move up')}\n >\n \u2191\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7\"\n disabled={idx === pipelineStages.length - 1}\n onClick={() => void handleMoveStage(stage, 'down')}\n title={t('customers.pipelines.stages.moveDown', 'Move down')}\n >\n \u2193\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => openEditStage(stage)}\n >\n {t('customers.pipelines.stages.edit', 'Edit')}\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-destructive hover:text-destructive\"\n onClick={() => void handleDeleteStage(stage)}\n >\n {t('customers.pipelines.stages.delete', 'Delete')}\n </Button>\n </div>\n </div>\n ))}\n </div>\n )}\n\n <div className=\"mt-4 flex items-center justify-end gap-2 border-t pt-3\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => openEditPipeline(pipeline)}\n >\n {t('customers.pipelines.actions.edit', 'Edit pipeline')}\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-destructive hover:text-destructive\"\n onClick={() => void handleDeletePipeline(pipeline)}\n >\n {t('customers.pipelines.actions.delete', 'Delete pipeline')}\n </Button>\n </div>\n </AccordionContent>\n </AccordionItem>\n )\n })}\n </Accordion>\n )}\n\n {/* Pipeline Dialog */}\n <Dialog open={pipelineDialog !== null} onOpenChange={(open) => { if (!open) closePipelineDialog() }}>\n <DialogContent onKeyDown={handleKeyDown(handlePipelineSubmit)}>\n <DialogHeader>\n <DialogTitle>\n {pipelineDialog?.mode === 'create'\n ? t('customers.pipelines.dialog.createTitle', 'Create pipeline')\n : t('customers.pipelines.dialog.editTitle', 'Edit pipeline')}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"pipeline-name\">{t('customers.pipelines.form.name', 'Name')}</Label>\n <Input\n id=\"pipeline-name\"\n value={pipelineForm.name}\n onChange={(e) => setPipelineForm((prev) => ({ ...prev, name: e.target.value }))}\n placeholder={t('customers.pipelines.form.namePlaceholder', 'e.g. New Business')}\n autoFocus\n />\n </div>\n <div className=\"flex items-center gap-2\">\n <Checkbox\n id=\"pipeline-default\"\n checked={pipelineForm.isDefault}\n onCheckedChange={(checked) => setPipelineForm((prev) => ({ ...prev, isDefault: checked === true }))}\n />\n <Label htmlFor=\"pipeline-default\" className=\"cursor-pointer\">\n {t('customers.pipelines.form.isDefault', 'Set as default pipeline')}\n </Label>\n </div>\n </div>\n <DialogFooter>\n <Button variant=\"outline\" onClick={closePipelineDialog} disabled={submittingPipeline}>\n {t('customers.pipelines.dialog.cancel', 'Cancel')}\n </Button>\n <Button onClick={() => void handlePipelineSubmit()} disabled={submittingPipeline || !pipelineForm.name.trim()}>\n {submittingPipeline ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('customers.pipelines.dialog.save', 'Save')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n\n {ConfirmDialogElement}\n\n {/* Stage Dialog */}\n <Dialog open={stageDialog !== null} onOpenChange={(open) => { if (!open) closeStageDialog() }}>\n <DialogContent onKeyDown={handleKeyDown(handleStageSubmit)}>\n <DialogHeader>\n <DialogTitle>\n {stageDialog?.mode === 'create'\n ? t('customers.pipelines.stageDialog.createTitle', 'Add stage')\n : t('customers.pipelines.stageDialog.editTitle', 'Edit stage')}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"stage-label\">{t('customers.pipelines.stageForm.label', 'Label')}</Label>\n <Input\n id=\"stage-label\"\n value={stageForm.label}\n onChange={(e) => setStageForm((prev) => ({ ...prev, label: e.target.value }))}\n placeholder={t('customers.pipelines.stageForm.labelPlaceholder', 'e.g. Discovery')}\n autoFocus\n />\n </div>\n <AppearanceSelector\n color={stageForm.color}\n icon={stageForm.icon}\n onColorChange={(next) => setStageForm((prev) => ({ ...prev, color: next }))}\n onIconChange={(next) => setStageForm((prev) => ({ ...prev, icon: next }))}\n labels={appearanceLabels}\n />\n </div>\n <DialogFooter>\n <Button variant=\"outline\" onClick={closeStageDialog} disabled={submittingStage}>\n {t('customers.pipelines.stageDialog.cancel', 'Cancel')}\n </Button>\n <Button onClick={() => void handleStageSubmit()} disabled={submittingStage || !stageForm.label.trim()}>\n {submittingStage ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('customers.pipelines.stageDialog.save', 'Save')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport {\n Dialog,\n DialogContent,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\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 { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from '@open-mercato/ui/primitives/accordion'\nimport {\n AppearanceSelector,\n type AppearanceSelectorLabels,\n} from '@open-mercato/core/modules/dictionaries/components/AppearanceSelector'\nimport {\n renderDictionaryColor,\n renderDictionaryIcon,\n} from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\n\ntype Pipeline = {\n id: string\n name: string\n isDefault: boolean\n organizationId: string\n tenantId: string\n updatedAt?: string | null\n}\n\ntype PipelineStage = {\n id: string\n pipelineId: string\n label: string\n order: number\n color: string | null\n icon: string | null\n updatedAt?: string | null\n}\n\ntype PipelineDialogState =\n | { mode: 'create' }\n | { mode: 'edit'; entry: Pipeline }\n\ntype StageDialogState =\n | { mode: 'create'; pipelineId: string }\n | { mode: 'edit'; entry: PipelineStage }\n\nfunction normalizePipeline(raw: Record<string, unknown>): Pipeline {\n return {\n id: typeof raw.id === 'string' ? raw.id : '',\n name: typeof raw.name === 'string' ? raw.name : '',\n isDefault: raw.isDefault === true || raw.is_default === true,\n organizationId: typeof raw.organizationId === 'string' ? raw.organizationId : (typeof raw.organization_id === 'string' ? raw.organization_id : ''),\n tenantId: typeof raw.tenantId === 'string' ? raw.tenantId : (typeof raw.tenant_id === 'string' ? raw.tenant_id : ''),\n updatedAt: typeof raw.updatedAt === 'string' ? raw.updatedAt : (typeof raw.updated_at === 'string' ? raw.updated_at : null),\n }\n}\n\nfunction normalizeStage(raw: Record<string, unknown>): PipelineStage {\n return {\n id: typeof raw.id === 'string' ? raw.id : '',\n pipelineId: typeof raw.pipelineId === 'string' ? raw.pipelineId : (typeof raw.pipeline_id === 'string' ? raw.pipeline_id : ''),\n label: typeof raw.label === 'string' ? raw.label : '',\n order: typeof raw.order === 'number' ? raw.order : 0,\n color: typeof raw.color === 'string' && raw.color.trim().length ? raw.color.trim() : null,\n icon: typeof raw.icon === 'string' && raw.icon.trim().length ? raw.icon.trim() : null,\n updatedAt: typeof raw.updatedAt === 'string' ? raw.updatedAt : (typeof raw.updated_at === 'string' ? raw.updated_at : null),\n }\n}\n\nexport default function PipelineSettings(): React.ReactElement {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const [pipelines, setPipelines] = React.useState<Pipeline[]>([])\n const [loadingPipelines, setLoadingPipelines] = React.useState(false)\n const [pipelineDialog, setPipelineDialog] = React.useState<PipelineDialogState | null>(null)\n const [pipelineForm, setPipelineForm] = React.useState({ name: '', isDefault: false })\n const [submittingPipeline, setSubmittingPipeline] = React.useState(false)\n\n const [expandedPipelineId, setExpandedPipelineId] = React.useState<string | null>(null)\n const [stages, setStages] = React.useState<Record<string, PipelineStage[]>>({})\n const [loadingStages, setLoadingStages] = React.useState<Record<string, boolean>>({})\n const [stageDialog, setStageDialog] = React.useState<StageDialogState | null>(null)\n const [stageForm, setStageForm] = React.useState({ label: '', color: null as string | null, icon: null as string | null })\n const [submittingStage, setSubmittingStage] = React.useState(false)\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: 'customers-pipeline-settings',\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n const pipelineMutationContext = React.useMemo(\n () => ({\n formId: 'customers-pipeline-settings',\n resourceKind: 'customers.pipeline',\n retryLastMutation,\n }),\n [retryLastMutation],\n )\n const stageMutationContext = React.useMemo(\n () => ({\n formId: 'customers-pipeline-settings',\n resourceKind: 'customers.pipeline_stage',\n retryLastMutation,\n }),\n [retryLastMutation],\n )\n\n const loadPipelines = React.useCallback(async () => {\n setLoadingPipelines(true)\n try {\n const data = await readApiResultOrThrow<{ items?: unknown[] }>(\n '/api/customers/pipelines',\n undefined,\n { errorMessage: t('customers.pipelines.errors.loadFailed', 'Failed to load pipelines'), fallback: { items: [] } },\n )\n const items = Array.isArray(data?.items) ? data.items : []\n setPipelines(items.map((item) => normalizePipeline(item as Record<string, unknown>)))\n } finally {\n setLoadingPipelines(false)\n }\n }, [t])\n\n const loadStages = React.useCallback(async (pipelineId: string) => {\n setLoadingStages((prev) => ({ ...prev, [pipelineId]: true }))\n try {\n const data = await readApiResultOrThrow<{ items?: unknown[] }>(\n `/api/customers/pipeline-stages?pipelineId=${encodeURIComponent(pipelineId)}`,\n undefined,\n { errorMessage: t('customers.pipelines.errors.stagesLoadFailed', 'Failed to load stages'), fallback: { items: [] } },\n )\n const items = Array.isArray(data?.items) ? data.items : []\n setStages((prev) => ({\n ...prev,\n [pipelineId]: items.map((item) => normalizeStage(item as Record<string, unknown>)),\n }))\n } finally {\n setLoadingStages((prev) => ({ ...prev, [pipelineId]: false }))\n }\n }, [t])\n\n React.useEffect(() => {\n void loadPipelines()\n }, [loadPipelines, scopeVersion])\n\n React.useEffect(() => {\n if (expandedPipelineId) {\n void loadStages(expandedPipelineId)\n }\n }, [expandedPipelineId, loadStages])\n\n const openCreatePipeline = React.useCallback(() => {\n setPipelineForm({ name: '', isDefault: false })\n setPipelineDialog({ mode: 'create' })\n }, [])\n\n const openEditPipeline = React.useCallback((pipeline: Pipeline) => {\n setPipelineForm({ name: pipeline.name, isDefault: pipeline.isDefault })\n setPipelineDialog({ mode: 'edit', entry: pipeline })\n }, [])\n\n const closePipelineDialog = React.useCallback(() => {\n setPipelineDialog(null)\n }, [])\n\n const handlePipelineSubmit = React.useCallback(async () => {\n if (!pipelineForm.name.trim()) return\n setSubmittingPipeline(true)\n try {\n if (pipelineDialog?.mode === 'create') {\n await runMutation({\n operation: async () => {\n const res = await apiCall('/api/customers/pipelines', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ name: pipelineForm.name.trim(), isDefault: pipelineForm.isDefault }),\n })\n if (!res.ok) {\n await raiseCrudError(res.response, t('customers.pipelines.errors.createFailed', 'Failed to create pipeline'))\n }\n },\n context: pipelineMutationContext,\n mutationPayload: { action: 'create', name: pipelineForm.name.trim(), isDefault: pipelineForm.isDefault },\n })\n flash(t('customers.pipelines.flash.created', 'Pipeline created'), 'success')\n } else if (pipelineDialog?.mode === 'edit') {\n await runMutation({\n operation: async () => {\n const res = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(pipelineDialog.entry.updatedAt),\n () =>\n apiCall('/api/customers/pipelines', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: pipelineDialog.entry.id, name: pipelineForm.name.trim(), isDefault: pipelineForm.isDefault }),\n }),\n )\n if (!res.ok) {\n await raiseCrudError(res.response, t('customers.pipelines.errors.updateFailed', 'Failed to update pipeline'))\n }\n },\n context: pipelineMutationContext,\n mutationPayload: { action: 'update', id: pipelineDialog.entry.id, name: pipelineForm.name.trim(), isDefault: pipelineForm.isDefault },\n })\n flash(t('customers.pipelines.flash.updated', 'Pipeline updated'), 'success')\n }\n setPipelineDialog(null)\n await loadPipelines()\n } catch {\n return\n } finally {\n setSubmittingPipeline(false)\n }\n }, [pipelineDialog, pipelineForm, loadPipelines, pipelineMutationContext, runMutation, t])\n\n const handleDeletePipeline = React.useCallback(async (pipeline: Pipeline) => {\n const confirmed = await confirm({\n title: t('customers.pipelines.confirm.deleteTitle', 'Delete pipeline'),\n text: t('customers.pipelines.confirm.deleteDesc', 'Are you sure you want to delete this pipeline? This cannot be undone.'),\n confirmText: t('customers.pipelines.confirm.deleteConfirm', 'Delete'),\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n await runMutation({\n operation: async () => {\n const res = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(pipeline.updatedAt),\n () =>\n apiCall('/api/customers/pipelines', {\n method: 'DELETE',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: pipeline.id }),\n }),\n )\n if (!res.ok) {\n const body = (res.result ?? {}) as Record<string, unknown>\n const msg = typeof body.error === 'string' ? body.error : t('customers.pipelines.errors.deleteFailed', 'Failed to delete pipeline')\n throw new Error(msg)\n }\n },\n context: pipelineMutationContext,\n mutationPayload: { action: 'delete', id: pipeline.id },\n })\n } catch (err) {\n const msg = err instanceof Error && err.message ? err.message : t('customers.pipelines.errors.deleteFailed', 'Failed to delete pipeline')\n flash(msg, 'error')\n return\n }\n flash(t('customers.pipelines.flash.deleted', 'Pipeline deleted'), 'success')\n if (expandedPipelineId === pipeline.id) setExpandedPipelineId(null)\n await loadPipelines()\n }, [confirm, expandedPipelineId, loadPipelines, pipelineMutationContext, runMutation, t])\n\n const openCreateStage = React.useCallback((pipelineId: string) => {\n setStageForm({ label: '', color: null, icon: null })\n setStageDialog({ mode: 'create', pipelineId })\n }, [])\n\n const openEditStage = React.useCallback((stage: PipelineStage) => {\n setStageForm({ label: stage.label, color: stage.color, icon: stage.icon })\n setStageDialog({ mode: 'edit', entry: stage })\n }, [])\n\n const closeStageDialog = React.useCallback(() => {\n setStageDialog(null)\n }, [])\n\n const handleStageSubmit = React.useCallback(async () => {\n if (!stageForm.label.trim()) return\n setSubmittingStage(true)\n try {\n const appearance: Record<string, unknown> = {}\n if (stageForm.color) appearance.color = stageForm.color\n if (stageForm.icon) appearance.icon = stageForm.icon\n\n if (stageDialog?.mode === 'create') {\n const targetPipelineId = stageDialog.pipelineId\n await runMutation({\n operation: async () => {\n const res = await apiCall('/api/customers/pipeline-stages', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ pipelineId: targetPipelineId, label: stageForm.label.trim(), ...appearance }),\n })\n if (!res.ok) {\n await raiseCrudError(res.response, t('customers.pipelines.errors.stageCreateFailed', 'Failed to create stage'))\n }\n },\n context: stageMutationContext,\n mutationPayload: { action: 'create', pipelineId: targetPipelineId, label: stageForm.label.trim(), ...appearance },\n })\n flash(t('customers.pipelines.flash.stageCreated', 'Stage created'), 'success')\n await loadStages(targetPipelineId)\n } else if (stageDialog?.mode === 'edit') {\n const targetStage = stageDialog.entry\n await runMutation({\n operation: async () => {\n const res = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(targetStage.updatedAt),\n () =>\n apiCall('/api/customers/pipeline-stages', {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: targetStage.id, label: stageForm.label.trim(), ...appearance }),\n }),\n )\n if (!res.ok) {\n await raiseCrudError(res.response, t('customers.pipelines.errors.stageUpdateFailed', 'Failed to update stage'))\n }\n },\n context: stageMutationContext,\n mutationPayload: { action: 'update', id: targetStage.id, label: stageForm.label.trim(), ...appearance },\n })\n flash(t('customers.pipelines.flash.stageUpdated', 'Stage updated'), 'success')\n await loadStages(targetStage.pipelineId)\n }\n setStageDialog(null)\n } catch {\n return\n } finally {\n setSubmittingStage(false)\n }\n }, [stageDialog, stageForm, loadStages, runMutation, stageMutationContext, t])\n\n const handleDeleteStage = React.useCallback(async (stage: PipelineStage) => {\n const confirmed = await confirm({\n title: t('customers.pipelines.confirm.stageDeleteTitle', 'Delete stage'),\n text: t('customers.pipelines.confirm.stageDeleteDesc', 'Are you sure you want to delete this stage?'),\n confirmText: t('customers.pipelines.confirm.stageDeleteConfirm', 'Delete'),\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n await runMutation({\n operation: async () => {\n const res = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(stage.updatedAt),\n () =>\n apiCall('/api/customers/pipeline-stages', {\n method: 'DELETE',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: stage.id }),\n }),\n )\n if (!res.ok) {\n const body = (res.result ?? {}) as Record<string, unknown>\n const msg = typeof body.error === 'string' ? body.error : t('customers.pipelines.errors.stageDeleteFailed', 'Failed to delete stage')\n throw new Error(msg)\n }\n },\n context: stageMutationContext,\n mutationPayload: { action: 'delete', id: stage.id },\n })\n } catch (err) {\n const msg = err instanceof Error && err.message ? err.message : t('customers.pipelines.errors.stageDeleteFailed', 'Failed to delete stage')\n flash(msg, 'error')\n return\n }\n flash(t('customers.pipelines.flash.stageDeleted', 'Stage deleted'), 'success')\n await loadStages(stage.pipelineId)\n }, [confirm, loadStages, runMutation, stageMutationContext, t])\n\n const handleMoveStage = React.useCallback(async (stage: PipelineStage, direction: 'up' | 'down') => {\n const pipelineStages = stages[stage.pipelineId] ?? []\n const idx = pipelineStages.findIndex((s) => s.id === stage.id)\n if (idx < 0) return\n const swapIdx = direction === 'up' ? idx - 1 : idx + 1\n if (swapIdx < 0 || swapIdx >= pipelineStages.length) return\n\n const reordered = [...pipelineStages]\n const temp = reordered[idx]\n reordered[idx] = reordered[swapIdx]\n reordered[swapIdx] = temp\n\n const orderedStages = reordered.map((s, i) => ({ id: s.id, order: i }))\n try {\n await runMutation({\n operation: async () => {\n const res = await apiCall('/api/customers/pipeline-stages/reorder', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ stages: orderedStages }),\n })\n if (!res.ok) {\n throw new Error(t('customers.pipelines.errors.reorderFailed', 'Failed to reorder stages'))\n }\n },\n context: stageMutationContext,\n mutationPayload: { action: 'reorder', stages: orderedStages },\n })\n } catch (err) {\n const msg = err instanceof Error && err.message ? err.message : t('customers.pipelines.errors.reorderFailed', 'Failed to reorder stages')\n flash(msg, 'error')\n return\n }\n await loadStages(stage.pipelineId)\n }, [stages, loadStages, runMutation, stageMutationContext, t])\n\n const handleKeyDown = React.useCallback(\n (handler: () => void) => (e: React.KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') {\n e.preventDefault()\n handler()\n }\n },\n [],\n )\n\n const appearanceLabels = React.useMemo<AppearanceSelectorLabels>(() => ({\n colorLabel: t('customers.pipelines.stageForm.color', 'Color'),\n colorClearLabel: t('customers.pipelines.stageForm.colorClear', 'Remove color'),\n iconLabel: t('customers.pipelines.stageForm.icon', 'Icon'),\n iconPlaceholder: t('customers.pipelines.stageForm.iconPlaceholder', 'e.g. lucide:star'),\n iconPickerTriggerLabel: t('customers.pipelines.stageForm.iconPicker', 'Pick icon'),\n iconSearchPlaceholder: t('customers.pipelines.stageForm.iconSearch', 'Search icons\u2026'),\n iconSearchEmptyLabel: t('customers.pipelines.stageForm.iconSearchEmpty', 'No icons found'),\n iconSuggestionsLabel: t('customers.pipelines.stageForm.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('customers.pipelines.stageForm.iconClear', 'Remove icon'),\n previewEmptyLabel: t('customers.pipelines.stageForm.previewEmpty', 'No appearance set'),\n }), [t])\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h3 className=\"text-base font-semibold\">{t('customers.pipelines.title', 'Sales Pipelines')}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {t('customers.pipelines.description', 'Manage sales pipelines and their stages.')}\n </p>\n </div>\n <Button size=\"sm\" onClick={openCreatePipeline}>\n {t('customers.pipelines.actions.create', 'Add pipeline')}\n </Button>\n </div>\n\n {loadingPipelines ? (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner className=\"h-4 w-4\" />\n {t('customers.pipelines.loading', 'Loading pipelines\u2026')}\n </div>\n ) : pipelines.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('customers.pipelines.empty', 'No pipelines yet. Create one to get started.')}\n </p>\n ) : (\n <Accordion\n type=\"single\"\n collapsible\n value={expandedPipelineId ?? ''}\n onValueChange={(next) => setExpandedPipelineId(next || null)}\n className=\"space-y-2\"\n >\n {pipelines.map((pipeline) => {\n const pipelineStages = stages[pipeline.id] ?? []\n const isLoadingStages = loadingStages[pipeline.id] ?? false\n\n return (\n <AccordionItem key={pipeline.id} value={pipeline.id}>\n <AccordionTrigger triggerIcon=\"chevron\">\n <span className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium\">{pipeline.name}</span>\n {pipeline.isDefault ? (\n <span className=\"rounded-full bg-primary/10 px-2 py-0.5 text-xs text-primary\">\n {t('customers.pipelines.defaultBadge', 'Default')}\n </span>\n ) : null}\n </span>\n </AccordionTrigger>\n <AccordionContent>\n <div className=\"mb-3 flex items-center justify-between\">\n <span className=\"text-xs font-medium text-muted-foreground uppercase tracking-wide\">\n {t('customers.pipelines.stages.title', 'Stages')}\n </span>\n <Button size=\"sm\" variant=\"outline\" onClick={() => openCreateStage(pipeline.id)}>\n {t('customers.pipelines.stages.add', 'Add stage')}\n </Button>\n </div>\n\n {isLoadingStages ? (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner className=\"h-3 w-3\" />\n {t('customers.pipelines.stages.loading', 'Loading\u2026')}\n </div>\n ) : pipelineStages.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('customers.pipelines.stages.empty', 'No stages yet.')}\n </p>\n ) : (\n <div className=\"divide-y divide-border rounded-md border bg-background\">\n {pipelineStages.map((stage, idx) => (\n <div key={stage.id} className=\"flex items-center justify-between gap-3 px-3 py-2\">\n <div className=\"flex items-center gap-2\">\n <span className=\"w-5 text-center text-xs text-muted-foreground\">{idx + 1}</span>\n {stage.color ? renderDictionaryColor(stage.color, 'h-3 w-3 rounded-full') : null}\n {stage.icon ? renderDictionaryIcon(stage.icon, 'h-4 w-4') : null}\n <span className=\"text-sm\">{stage.label}</span>\n </div>\n <div className=\"flex items-center gap-1\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7\"\n disabled={idx === 0}\n onClick={() => void handleMoveStage(stage, 'up')}\n title={t('customers.pipelines.stages.moveUp', 'Move up')}\n >\n \u2191\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7\"\n disabled={idx === pipelineStages.length - 1}\n onClick={() => void handleMoveStage(stage, 'down')}\n title={t('customers.pipelines.stages.moveDown', 'Move down')}\n >\n \u2193\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => openEditStage(stage)}\n >\n {t('customers.pipelines.stages.edit', 'Edit')}\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-destructive hover:text-destructive\"\n onClick={() => void handleDeleteStage(stage)}\n >\n {t('customers.pipelines.stages.delete', 'Delete')}\n </Button>\n </div>\n </div>\n ))}\n </div>\n )}\n\n <div className=\"mt-4 flex items-center justify-end gap-2 border-t pt-3\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => openEditPipeline(pipeline)}\n >\n {t('customers.pipelines.actions.edit', 'Edit pipeline')}\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-destructive hover:text-destructive\"\n onClick={() => void handleDeletePipeline(pipeline)}\n >\n {t('customers.pipelines.actions.delete', 'Delete pipeline')}\n </Button>\n </div>\n </AccordionContent>\n </AccordionItem>\n )\n })}\n </Accordion>\n )}\n\n {/* Pipeline Dialog */}\n <Dialog open={pipelineDialog !== null} onOpenChange={(open) => { if (!open) closePipelineDialog() }}>\n <DialogContent onKeyDown={handleKeyDown(handlePipelineSubmit)}>\n <DialogHeader>\n <DialogTitle>\n {pipelineDialog?.mode === 'create'\n ? t('customers.pipelines.dialog.createTitle', 'Create pipeline')\n : t('customers.pipelines.dialog.editTitle', 'Edit pipeline')}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"pipeline-name\">{t('customers.pipelines.form.name', 'Name')}</Label>\n <Input\n id=\"pipeline-name\"\n value={pipelineForm.name}\n onChange={(e) => setPipelineForm((prev) => ({ ...prev, name: e.target.value }))}\n placeholder={t('customers.pipelines.form.namePlaceholder', 'e.g. New Business')}\n autoFocus\n />\n </div>\n <div className=\"flex items-center gap-2\">\n <Checkbox\n id=\"pipeline-default\"\n checked={pipelineForm.isDefault}\n onCheckedChange={(checked) => setPipelineForm((prev) => ({ ...prev, isDefault: checked === true }))}\n />\n <Label htmlFor=\"pipeline-default\" className=\"cursor-pointer\">\n {t('customers.pipelines.form.isDefault', 'Set as default pipeline')}\n </Label>\n </div>\n </div>\n <DialogFooter>\n <Button variant=\"outline\" onClick={closePipelineDialog} disabled={submittingPipeline}>\n {t('customers.pipelines.dialog.cancel', 'Cancel')}\n </Button>\n <Button onClick={() => void handlePipelineSubmit()} disabled={submittingPipeline || !pipelineForm.name.trim()}>\n {submittingPipeline ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('customers.pipelines.dialog.save', 'Save')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n\n {ConfirmDialogElement}\n\n {/* Stage Dialog */}\n <Dialog open={stageDialog !== null} onOpenChange={(open) => { if (!open) closeStageDialog() }}>\n <DialogContent onKeyDown={handleKeyDown(handleStageSubmit)}>\n <DialogHeader>\n <DialogTitle>\n {stageDialog?.mode === 'create'\n ? t('customers.pipelines.stageDialog.createTitle', 'Add stage')\n : t('customers.pipelines.stageDialog.editTitle', 'Edit stage')}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-2\">\n <Label htmlFor=\"stage-label\">{t('customers.pipelines.stageForm.label', 'Label')}</Label>\n <Input\n id=\"stage-label\"\n value={stageForm.label}\n onChange={(e) => setStageForm((prev) => ({ ...prev, label: e.target.value }))}\n placeholder={t('customers.pipelines.stageForm.labelPlaceholder', 'e.g. Discovery')}\n autoFocus\n />\n </div>\n <AppearanceSelector\n color={stageForm.color}\n icon={stageForm.icon}\n onColorChange={(next) => setStageForm((prev) => ({ ...prev, color: next }))}\n onIconChange={(next) => setStageForm((prev) => ({ ...prev, icon: next }))}\n labels={appearanceLabels}\n />\n </div>\n <DialogFooter>\n <Button variant=\"outline\" onClick={closeStageDialog} disabled={submittingStage}>\n {t('customers.pipelines.stageDialog.cancel', 'Cancel')}\n </Button>\n <Button onClick={() => void handleStageSubmit()} disabled={submittingStage || !stageForm.label.trim()}>\n {submittingStage ? <Spinner className=\"mr-2 h-4 w-4\" /> : null}\n {t('customers.pipelines.stageDialog.save', 'Save')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAgcQ,SACE,KADF;AA9bR,YAAY,WAAW;AACvB,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB,SAAS,aAAa;AACtB,SAAS,gBAAgB;AACzB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,aAAa;AACtB,SAAS,SAAS,sBAAsB,mCAAmC;AAC3E,SAAS,iCAAiC;AAC1C,SAAS,sBAAsB;AAC/B,SAAS,0BAA0B;AACnC,SAAS,mCAAmC;AAC5C,SAAS,YAAY;AACrB,SAAS,wBAAwB;AACjC,SAAS,eAAe;AACxB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AA6BP,SAAS,kBAAkB,KAAwC;AACjE,SAAO;AAAA,IACL,IAAI,OAAO,IAAI,OAAO,WAAW,IAAI,KAAK;AAAA,IAC1C,MAAM,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO;AAAA,IAChD,WAAW,IAAI,cAAc,QAAQ,IAAI,eAAe;AAAA,IACxD,gBAAgB,OAAO,IAAI,mBAAmB,WAAW,IAAI,iBAAkB,OAAO,IAAI,oBAAoB,WAAW,IAAI,kBAAkB;AAAA,IAC/I,UAAU,OAAO,IAAI,aAAa,WAAW,IAAI,WAAY,OAAO,IAAI,cAAc,WAAW,IAAI,YAAY;AAAA,IACjH,WAAW,OAAO,IAAI,cAAc,WAAW,IAAI,YAAa,OAAO,IAAI,eAAe,WAAW,IAAI,aAAa;AAAA,EACxH;AACF;AAEA,SAAS,eAAe,KAA6C;AACnE,SAAO;AAAA,IACL,IAAI,OAAO,IAAI,OAAO,WAAW,IAAI,KAAK;AAAA,IAC1C,YAAY,OAAO,IAAI,eAAe,WAAW,IAAI,aAAc,OAAO,IAAI,gBAAgB,WAAW,IAAI,cAAc;AAAA,IAC3H,OAAO,OAAO,IAAI,UAAU,WAAW,IAAI,QAAQ;AAAA,IACnD,OAAO,OAAO,IAAI,UAAU,WAAW,IAAI,QAAQ;AAAA,IACnD,OAAO,OAAO,IAAI,UAAU,YAAY,IAAI,MAAM,KAAK,EAAE,SAAS,IAAI,MAAM,KAAK,IAAI;AAAA,IACrF,MAAM,OAAO,IAAI,SAAS,YAAY,IAAI,KAAK,KAAK,EAAE,SAAS,IAAI,KAAK,KAAK,IAAI;AAAA,IACjF,WAAW,OAAO,IAAI,cAAc,WAAW,IAAI,YAAa,OAAO,IAAI,eAAe,WAAW,IAAI,aAAa;AAAA,EACxH;AACF;AAEe,SAAR,mBAAwD;AAC7D,QAAM,IAAI,KAAK;AACf,QAAM,eAAe,4BAA4B;AACjD,QAAM,EAAE,SAAS,qBAAqB,IAAI,iBAAiB;AAE3D,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAqB,CAAC,CAAC;AAC/D,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAS,KAAK;AACpE,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAqC,IAAI;AAC3F,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAS,EAAE,MAAM,IAAI,WAAW,MAAM,CAAC;AACrF,QAAM,CAAC,oBAAoB,qBAAqB,IAAI,MAAM,SAAS,KAAK;AAExE,QAAM,CAAC,oBAAoB,qBAAqB,IAAI,MAAM,SAAwB,IAAI;AACtF,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAA0C,CAAC,CAAC;AAC9E,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAkC,CAAC,CAAC;AACpF,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAkC,IAAI;AAClF,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,EAAE,OAAO,IAAI,OAAO,MAAuB,MAAM,KAAsB,CAAC;AACzH,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,KAAK;AAElE,QAAM,EAAE,aAAa,kBAAkB,IAAI,mBAIxC;AAAA,IACD,WAAW;AAAA,IACX,gBAAgB,EAAE,8BAA8B,4BAA4B;AAAA,EAC9E,CAAC;AACD,QAAM,0BAA0B,MAAM;AAAA,IACpC,OAAO;AAAA,MACL,QAAQ;AAAA,MACR,cAAc;AAAA,MACd;AAAA,IACF;AAAA,IACA,CAAC,iBAAiB;AAAA,EACpB;AACA,QAAM,uBAAuB,MAAM;AAAA,IACjC,OAAO;AAAA,MACL,QAAQ;AAAA,MACR,cAAc;AAAA,MACd;AAAA,IACF;AAAA,IACA,CAAC,iBAAiB;AAAA,EACpB;AAEA,QAAM,gBAAgB,MAAM,YAAY,YAAY;AAClD,wBAAoB,IAAI;AACxB,QAAI;AACF,YAAM,OAAO,MAAM;AAAA,QACjB;AAAA,QACA;AAAA,QACA,EAAE,cAAc,EAAE,yCAAyC,0BAA0B,GAAG,UAAU,EAAE,OAAO,CAAC,EAAE,EAAE;AAAA,MAClH;AACA,YAAM,QAAQ,MAAM,QAAQ,MAAM,KAAK,IAAI,KAAK,QAAQ,CAAC;AACzD,mBAAa,MAAM,IAAI,CAAC,SAAS,kBAAkB,IAA+B,CAAC,CAAC;AAAA,IACtF,UAAE;AACA,0BAAoB,KAAK;AAAA,IAC3B;AAAA,EACF,GAAG,CAAC,CAAC,CAAC;AAEN,QAAM,aAAa,MAAM,YAAY,OAAO,eAAuB;AACjE,qBAAiB,CAAC,UAAU,EAAE,GAAG,MAAM,CAAC,UAAU,GAAG,KAAK,EAAE;AAC5D,QAAI;AACF,YAAM,OAAO,MAAM;AAAA,QACjB,6CAA6C,mBAAmB,UAAU,CAAC;AAAA,QAC3E;AAAA,QACA,EAAE,cAAc,EAAE,+CAA+C,uBAAuB,GAAG,UAAU,EAAE,OAAO,CAAC,EAAE,EAAE;AAAA,MACrH;AACA,YAAM,QAAQ,MAAM,QAAQ,MAAM,KAAK,IAAI,KAAK,QAAQ,CAAC;AACzD,gBAAU,CAAC,UAAU;AAAA,QACnB,GAAG;AAAA,QACH,CAAC,UAAU,GAAG,MAAM,IAAI,CAAC,SAAS,eAAe,IAA+B,CAAC;AAAA,MACnF,EAAE;AAAA,IACJ,UAAE;AACA,uBAAiB,CAAC,UAAU,EAAE,GAAG,MAAM,CAAC,UAAU,GAAG,MAAM,EAAE;AAAA,IAC/D;AAAA,EACF,GAAG,CAAC,CAAC,CAAC;AAEN,QAAM,UAAU,MAAM;AACpB,SAAK,cAAc;AAAA,EACrB,GAAG,CAAC,eAAe,YAAY,CAAC;AAEhC,QAAM,UAAU,MAAM;AACpB,QAAI,oBAAoB;AACtB,WAAK,WAAW,kBAAkB;AAAA,IACpC;AAAA,EACF,GAAG,CAAC,oBAAoB,UAAU,CAAC;AAEnC,QAAM,qBAAqB,MAAM,YAAY,MAAM;AACjD,oBAAgB,EAAE,MAAM,IAAI,WAAW,MAAM,CAAC;AAC9C,sBAAkB,EAAE,MAAM,SAAS,CAAC;AAAA,EACtC,GAAG,CAAC,CAAC;AAEL,QAAM,mBAAmB,MAAM,YAAY,CAAC,aAAuB;AACjE,oBAAgB,EAAE,MAAM,SAAS,MAAM,WAAW,SAAS,UAAU,CAAC;AACtE,sBAAkB,EAAE,MAAM,QAAQ,OAAO,SAAS,CAAC;AAAA,EACrD,GAAG,CAAC,CAAC;AAEL,QAAM,sBAAsB,MAAM,YAAY,MAAM;AAClD,sBAAkB,IAAI;AAAA,EACxB,GAAG,CAAC,CAAC;AAEL,QAAM,uBAAuB,MAAM,YAAY,YAAY;AACzD,QAAI,CAAC,aAAa,KAAK,KAAK,EAAG;AAC/B,0BAAsB,IAAI;AAC1B,QAAI;AACF,UAAI,gBAAgB,SAAS,UAAU;AACrC,cAAM,YAAY;AAAA,UAChB,WAAW,YAAY;AACrB,kBAAM,MAAM,MAAM,QAAQ,4BAA4B;AAAA,cACpD,QAAQ;AAAA,cACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,cAC9C,MAAM,KAAK,UAAU,EAAE,MAAM,aAAa,KAAK,KAAK,GAAG,WAAW,aAAa,UAAU,CAAC;AAAA,YAC5F,CAAC;AACD,gBAAI,CAAC,IAAI,IAAI;AACX,oBAAM,eAAe,IAAI,UAAU,EAAE,2CAA2C,2BAA2B,CAAC;AAAA,YAC9G;AAAA,UACF;AAAA,UACA,SAAS;AAAA,UACT,iBAAiB,EAAE,QAAQ,UAAU,MAAM,aAAa,KAAK,KAAK,GAAG,WAAW,aAAa,UAAU;AAAA,QACzG,CAAC;AACD,cAAM,EAAE,qCAAqC,kBAAkB,GAAG,SAAS;AAAA,MAC7E,WAAW,gBAAgB,SAAS,QAAQ;AAC1C,cAAM,YAAY;AAAA,UAChB,WAAW,YAAY;AACrB,kBAAM,MAAM,MAAM;AAAA,cAChB,0BAA0B,eAAe,MAAM,SAAS;AAAA,cACxD,MACE,QAAQ,4BAA4B;AAAA,gBAClC,QAAQ;AAAA,gBACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,gBAC9C,MAAM,KAAK,UAAU,EAAE,IAAI,eAAe,MAAM,IAAI,MAAM,aAAa,KAAK,KAAK,GAAG,WAAW,aAAa,UAAU,CAAC;AAAA,cACzH,CAAC;AAAA,YACL;AACA,gBAAI,CAAC,IAAI,IAAI;AACX,oBAAM,eAAe,IAAI,UAAU,EAAE,2CAA2C,2BAA2B,CAAC;AAAA,YAC9G;AAAA,UACF;AAAA,UACA,SAAS;AAAA,UACT,iBAAiB,EAAE,QAAQ,UAAU,IAAI,eAAe,MAAM,IAAI,MAAM,aAAa,KAAK,KAAK,GAAG,WAAW,aAAa,UAAU;AAAA,QACtI,CAAC;AACD,cAAM,EAAE,qCAAqC,kBAAkB,GAAG,SAAS;AAAA,MAC7E;AACA,wBAAkB,IAAI;AACtB,YAAM,cAAc;AAAA,IACtB,QAAQ;AACN;AAAA,IACF,UAAE;AACA,4BAAsB,KAAK;AAAA,IAC7B;AAAA,EACF,GAAG,CAAC,gBAAgB,cAAc,eAAe,yBAAyB,aAAa,CAAC,CAAC;AAEzF,QAAM,uBAAuB,MAAM,YAAY,OAAO,aAAuB;AAC3E,UAAM,YAAY,MAAM,QAAQ;AAAA,MAC9B,OAAO,EAAE,2CAA2C,iBAAiB;AAAA,MACrE,MAAM,EAAE,0CAA0C,uEAAuE;AAAA,MACzH,aAAa,EAAE,6CAA6C,QAAQ;AAAA,MACpE,SAAS;AAAA,IACX,CAAC;AACD,QAAI,CAAC,UAAW;AAChB,QAAI;AACF,YAAM,YAAY;AAAA,QAChB,WAAW,YAAY;AACrB,gBAAM,MAAM,MAAM;AAAA,YAChB,0BAA0B,SAAS,SAAS;AAAA,YAC5C,MACE,QAAQ,4BAA4B;AAAA,cAClC,QAAQ;AAAA,cACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,cAC9C,MAAM,KAAK,UAAU,EAAE,IAAI,SAAS,GAAG,CAAC;AAAA,YAC1C,CAAC;AAAA,UACL;AACA,cAAI,CAAC,IAAI,IAAI;AACX,kBAAM,OAAQ,IAAI,UAAU,CAAC;AAC7B,kBAAM,MAAM,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,EAAE,2CAA2C,2BAA2B;AAClI,kBAAM,IAAI,MAAM,GAAG;AAAA,UACrB;AAAA,QACF;AAAA,QACA,SAAS;AAAA,QACT,iBAAiB,EAAE,QAAQ,UAAU,IAAI,SAAS,GAAG;AAAA,MACvD,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,SAAS,IAAI,UAAU,IAAI,UAAU,EAAE,2CAA2C,2BAA2B;AACxI,YAAM,KAAK,OAAO;AAClB;AAAA,IACF;AACA,UAAM,EAAE,qCAAqC,kBAAkB,GAAG,SAAS;AAC3E,QAAI,uBAAuB,SAAS,GAAI,uBAAsB,IAAI;AAClE,UAAM,cAAc;AAAA,EACtB,GAAG,CAAC,SAAS,oBAAoB,eAAe,yBAAyB,aAAa,CAAC,CAAC;AAExF,QAAM,kBAAkB,MAAM,YAAY,CAAC,eAAuB;AAChE,iBAAa,EAAE,OAAO,IAAI,OAAO,MAAM,MAAM,KAAK,CAAC;AACnD,mBAAe,EAAE,MAAM,UAAU,WAAW,CAAC;AAAA,EAC/C,GAAG,CAAC,CAAC;AAEL,QAAM,gBAAgB,MAAM,YAAY,CAAC,UAAyB;AAChE,iBAAa,EAAE,OAAO,MAAM,OAAO,OAAO,MAAM,OAAO,MAAM,MAAM,KAAK,CAAC;AACzE,mBAAe,EAAE,MAAM,QAAQ,OAAO,MAAM,CAAC;AAAA,EAC/C,GAAG,CAAC,CAAC;AAEL,QAAM,mBAAmB,MAAM,YAAY,MAAM;AAC/C,mBAAe,IAAI;AAAA,EACrB,GAAG,CAAC,CAAC;AAEL,QAAM,oBAAoB,MAAM,YAAY,YAAY;AACtD,QAAI,CAAC,UAAU,MAAM,KAAK,EAAG;AAC7B,uBAAmB,IAAI;AACvB,QAAI;AACF,YAAM,aAAsC,CAAC;AAC7C,UAAI,UAAU,MAAO,YAAW,QAAQ,UAAU;AAClD,UAAI,UAAU,KAAM,YAAW,OAAO,UAAU;AAEhD,UAAI,aAAa,SAAS,UAAU;AAClC,cAAM,mBAAmB,YAAY;AACrC,cAAM,YAAY;AAAA,UAChB,WAAW,YAAY;AACrB,kBAAM,MAAM,MAAM,QAAQ,kCAAkC;AAAA,cAC1D,QAAQ;AAAA,cACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,cAC9C,MAAM,KAAK,UAAU,EAAE,YAAY,kBAAkB,OAAO,UAAU,MAAM,KAAK,GAAG,GAAG,WAAW,CAAC;AAAA,YACrG,CAAC;AACD,gBAAI,CAAC,IAAI,IAAI;AACX,oBAAM,eAAe,IAAI,UAAU,EAAE,gDAAgD,wBAAwB,CAAC;AAAA,YAChH;AAAA,UACF;AAAA,UACA,SAAS;AAAA,UACT,iBAAiB,EAAE,QAAQ,UAAU,YAAY,kBAAkB,OAAO,UAAU,MAAM,KAAK,GAAG,GAAG,WAAW;AAAA,QAClH,CAAC;AACD,cAAM,EAAE,0CAA0C,eAAe,GAAG,SAAS;AAC7E,cAAM,WAAW,gBAAgB;AAAA,MACnC,WAAW,aAAa,SAAS,QAAQ;AACvC,cAAM,cAAc,YAAY;AAChC,cAAM,YAAY;AAAA,UAChB,WAAW,YAAY;AACrB,kBAAM,MAAM,MAAM;AAAA,cAChB,0BAA0B,YAAY,SAAS;AAAA,cAC/C,MACE,QAAQ,kCAAkC;AAAA,gBACxC,QAAQ;AAAA,gBACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,gBAC9C,MAAM,KAAK,UAAU,EAAE,IAAI,YAAY,IAAI,OAAO,UAAU,MAAM,KAAK,GAAG,GAAG,WAAW,CAAC;AAAA,cAC3F,CAAC;AAAA,YACL;AACA,gBAAI,CAAC,IAAI,IAAI;AACX,oBAAM,eAAe,IAAI,UAAU,EAAE,gDAAgD,wBAAwB,CAAC;AAAA,YAChH;AAAA,UACF;AAAA,UACA,SAAS;AAAA,UACT,iBAAiB,EAAE,QAAQ,UAAU,IAAI,YAAY,IAAI,OAAO,UAAU,MAAM,KAAK,GAAG,GAAG,WAAW;AAAA,QACxG,CAAC;AACD,cAAM,EAAE,0CAA0C,eAAe,GAAG,SAAS;AAC7E,cAAM,WAAW,YAAY,UAAU;AAAA,MACzC;AACA,qBAAe,IAAI;AAAA,IACrB,QAAQ;AACN;AAAA,IACF,UAAE;AACA,yBAAmB,KAAK;AAAA,IAC1B;AAAA,EACF,GAAG,CAAC,aAAa,WAAW,YAAY,aAAa,sBAAsB,CAAC,CAAC;AAE7E,QAAM,oBAAoB,MAAM,YAAY,OAAO,UAAyB;AAC1E,UAAM,YAAY,MAAM,QAAQ;AAAA,MAC9B,OAAO,EAAE,gDAAgD,cAAc;AAAA,MACvE,MAAM,EAAE,+CAA+C,6CAA6C;AAAA,MACpG,aAAa,EAAE,kDAAkD,QAAQ;AAAA,MACzE,SAAS;AAAA,IACX,CAAC;AACD,QAAI,CAAC,UAAW;AAChB,QAAI;AACF,YAAM,YAAY;AAAA,QAChB,WAAW,YAAY;AACrB,gBAAM,MAAM,MAAM;AAAA,YAChB,0BAA0B,MAAM,SAAS;AAAA,YACzC,MACE,QAAQ,kCAAkC;AAAA,cACxC,QAAQ;AAAA,cACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,cAC9C,MAAM,KAAK,UAAU,EAAE,IAAI,MAAM,GAAG,CAAC;AAAA,YACvC,CAAC;AAAA,UACL;AACA,cAAI,CAAC,IAAI,IAAI;AACX,kBAAM,OAAQ,IAAI,UAAU,CAAC;AAC7B,kBAAM,MAAM,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,EAAE,gDAAgD,wBAAwB;AACpI,kBAAM,IAAI,MAAM,GAAG;AAAA,UACrB;AAAA,QACF;AAAA,QACA,SAAS;AAAA,QACT,iBAAiB,EAAE,QAAQ,UAAU,IAAI,MAAM,GAAG;AAAA,MACpD,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,SAAS,IAAI,UAAU,IAAI,UAAU,EAAE,gDAAgD,wBAAwB;AAC1I,YAAM,KAAK,OAAO;AAClB;AAAA,IACF;AACA,UAAM,EAAE,0CAA0C,eAAe,GAAG,SAAS;AAC7E,UAAM,WAAW,MAAM,UAAU;AAAA,EACnC,GAAG,CAAC,SAAS,YAAY,aAAa,sBAAsB,CAAC,CAAC;AAE9D,QAAM,kBAAkB,MAAM,YAAY,OAAO,OAAsB,cAA6B;AAClG,UAAM,iBAAiB,OAAO,MAAM,UAAU,KAAK,CAAC;AACpD,UAAM,MAAM,eAAe,UAAU,CAAC,MAAM,EAAE,OAAO,MAAM,EAAE;AAC7D,QAAI,MAAM,EAAG;AACb,UAAM,UAAU,cAAc,OAAO,MAAM,IAAI,MAAM;AACrD,QAAI,UAAU,KAAK,WAAW,eAAe,OAAQ;AAErD,UAAM,YAAY,CAAC,GAAG,cAAc;AACpC,UAAM,OAAO,UAAU,GAAG;AAC1B,cAAU,GAAG,IAAI,UAAU,OAAO;AAClC,cAAU,OAAO,IAAI;AAErB,UAAM,gBAAgB,UAAU,IAAI,CAAC,GAAG,OAAO,EAAE,IAAI,EAAE,IAAI,OAAO,EAAE,EAAE;AACtE,QAAI;AACF,YAAM,YAAY;AAAA,QAChB,WAAW,YAAY;AACrB,gBAAM,MAAM,MAAM,QAAQ,0CAA0C;AAAA,YAClE,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,YAC9C,MAAM,KAAK,UAAU,EAAE,QAAQ,cAAc,CAAC;AAAA,UAChD,CAAC;AACD,cAAI,CAAC,IAAI,IAAI;AACX,kBAAM,IAAI,MAAM,EAAE,4CAA4C,0BAA0B,CAAC;AAAA,UAC3F;AAAA,QACF;AAAA,QACA,SAAS;AAAA,QACT,iBAAiB,EAAE,QAAQ,WAAW,QAAQ,cAAc;AAAA,MAC9D,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,SAAS,IAAI,UAAU,IAAI,UAAU,EAAE,4CAA4C,0BAA0B;AACxI,YAAM,KAAK,OAAO;AAClB;AAAA,IACF;AACA,UAAM,WAAW,MAAM,UAAU;AAAA,EACnC,GAAG,CAAC,QAAQ,YAAY,aAAa,sBAAsB,CAAC,CAAC;AAE7D,QAAM,gBAAgB,MAAM;AAAA,IAC1B,CAAC,YAAwB,CAAC,MAA2B;AACnD,WAAK,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ,SAAS;AACjD,UAAE,eAAe;AACjB,gBAAQ;AAAA,MACV;AAAA,IACF;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,mBAAmB,MAAM,QAAkC,OAAO;AAAA,IACtE,YAAY,EAAE,uCAAuC,OAAO;AAAA,IAC5D,iBAAiB,EAAE,4CAA4C,cAAc;AAAA,IAC7E,WAAW,EAAE,sCAAsC,MAAM;AAAA,IACzD,iBAAiB,EAAE,iDAAiD,kBAAkB;AAAA,IACtF,wBAAwB,EAAE,4CAA4C,WAAW;AAAA,IACjF,uBAAuB,EAAE,4CAA4C,oBAAe;AAAA,IACpF,sBAAsB,EAAE,iDAAiD,gBAAgB;AAAA,IACzF,sBAAsB,EAAE,iDAAiD,aAAa;AAAA,IACtF,gBAAgB,EAAE,2CAA2C,aAAa;AAAA,IAC1E,mBAAmB,EAAE,8CAA8C,mBAAmB;AAAA,EACxF,IAAI,CAAC,CAAC,CAAC;AAEP,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,yBAAC,SAAI,WAAU,qCACb;AAAA,2BAAC,SACC;AAAA,4BAAC,QAAG,WAAU,2BAA2B,YAAE,6BAA6B,iBAAiB,GAAE;AAAA,QAC3F,oBAAC,OAAE,WAAU,iCACV,YAAE,mCAAmC,0CAA0C,GAClF;AAAA,SACF;AAAA,MACA,oBAAC,UAAO,MAAK,MAAK,SAAS,oBACxB,YAAE,sCAAsC,cAAc,GACzD;AAAA,OACF;AAAA,IAEC,mBACC,qBAAC,SAAI,WAAU,yDACb;AAAA,0BAAC,WAAQ,WAAU,WAAU;AAAA,MAC5B,EAAE,+BAA+B,yBAAoB;AAAA,OACxD,IACE,UAAU,WAAW,IACvB,oBAAC,OAAE,WAAU,iCACV,YAAE,6BAA6B,8CAA8C,GAChF,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,aAAW;AAAA,QACX,OAAO,sBAAsB;AAAA,QAC7B,eAAe,CAAC,SAAS,sBAAsB,QAAQ,IAAI;AAAA,QAC3D,WAAU;AAAA,QAET,oBAAU,IAAI,CAAC,aAAa;AAC3B,gBAAM,iBAAiB,OAAO,SAAS,EAAE,KAAK,CAAC;AAC/C,gBAAM,kBAAkB,cAAc,SAAS,EAAE,KAAK;AAEtD,iBACE,qBAAC,iBAAgC,OAAO,SAAS,IAC/C;AAAA,gCAAC,oBAAiB,aAAY,WAC5B,+BAAC,UAAK,WAAU,2BACd;AAAA,kCAAC,UAAK,WAAU,uBAAuB,mBAAS,MAAK;AAAA,cACpD,SAAS,YACR,oBAAC,UAAK,WAAU,+DACb,YAAE,oCAAoC,SAAS,GAClD,IACE;AAAA,eACN,GACF;AAAA,YACA,qBAAC,oBACC;AAAA,mCAAC,SAAI,WAAU,0CACb;AAAA,oCAAC,UAAK,WAAU,qEACb,YAAE,oCAAoC,QAAQ,GACjD;AAAA,gBACA,oBAAC,UAAO,MAAK,MAAK,SAAQ,WAAU,SAAS,MAAM,gBAAgB,SAAS,EAAE,GAC3E,YAAE,kCAAkC,WAAW,GAClD;AAAA,iBACF;AAAA,cAEC,kBACC,qBAAC,SAAI,WAAU,yDACb;AAAA,oCAAC,WAAQ,WAAU,WAAU;AAAA,gBAC5B,EAAE,sCAAsC,eAAU;AAAA,iBACrD,IACE,eAAe,WAAW,IAC5B,oBAAC,OAAE,WAAU,iCACV,YAAE,oCAAoC,gBAAgB,GACzD,IAEA,oBAAC,SAAI,WAAU,0DACZ,yBAAe,IAAI,CAAC,OAAO,QAC1B,qBAAC,SAAmB,WAAU,qDAC5B;AAAA,qCAAC,SAAI,WAAU,2BACb;AAAA,sCAAC,UAAK,WAAU,iDAAiD,gBAAM,GAAE;AAAA,kBACxE,MAAM,QAAQ,sBAAsB,MAAM,OAAO,sBAAsB,IAAI;AAAA,kBAC3E,MAAM,OAAO,qBAAqB,MAAM,MAAM,SAAS,IAAI;AAAA,kBAC5D,oBAAC,UAAK,WAAU,WAAW,gBAAM,OAAM;AAAA,mBACzC;AAAA,gBACA,qBAAC,SAAI,WAAU,2BACb;AAAA;AAAA,oBAAC;AAAA;AAAA,sBACC,SAAQ;AAAA,sBACR,MAAK;AAAA,sBACL,WAAU;AAAA,sBACV,UAAU,QAAQ;AAAA,sBAClB,SAAS,MAAM,KAAK,gBAAgB,OAAO,IAAI;AAAA,sBAC/C,OAAO,EAAE,qCAAqC,SAAS;AAAA,sBACxD;AAAA;AAAA,kBAED;AAAA,kBACA;AAAA,oBAAC;AAAA;AAAA,sBACC,SAAQ;AAAA,sBACR,MAAK;AAAA,sBACL,WAAU;AAAA,sBACV,UAAU,QAAQ,eAAe,SAAS;AAAA,sBAC1C,SAAS,MAAM,KAAK,gBAAgB,OAAO,MAAM;AAAA,sBACjD,OAAO,EAAE,uCAAuC,WAAW;AAAA,sBAC5D;AAAA;AAAA,kBAED;AAAA,kBACA;AAAA,oBAAC;AAAA;AAAA,sBACC,SAAQ;AAAA,sBACR,MAAK;AAAA,sBACL,SAAS,MAAM,cAAc,KAAK;AAAA,sBAEjC,YAAE,mCAAmC,MAAM;AAAA;AAAA,kBAC9C;AAAA,kBACA;AAAA,oBAAC;AAAA;AAAA,sBACC,SAAQ;AAAA,sBACR,MAAK;AAAA,sBACL,WAAU;AAAA,sBACV,SAAS,MAAM,KAAK,kBAAkB,KAAK;AAAA,sBAE1C,YAAE,qCAAqC,QAAQ;AAAA;AAAA,kBAClD;AAAA,mBACF;AAAA,mBA3CQ,MAAM,EA4ChB,CACD,GACH;AAAA,cAGF,qBAAC,SAAI,WAAU,0DACb;AAAA;AAAA,kBAAC;AAAA;AAAA,oBACC,SAAQ;AAAA,oBACR,MAAK;AAAA,oBACL,SAAS,MAAM,iBAAiB,QAAQ;AAAA,oBAEvC,YAAE,oCAAoC,eAAe;AAAA;AAAA,gBACxD;AAAA,gBACA;AAAA,kBAAC;AAAA;AAAA,oBACC,SAAQ;AAAA,oBACR,MAAK;AAAA,oBACL,WAAU;AAAA,oBACV,SAAS,MAAM,KAAK,qBAAqB,QAAQ;AAAA,oBAEhD,YAAE,sCAAsC,iBAAiB;AAAA;AAAA,gBAC5D;AAAA,iBACF;AAAA,eACF;AAAA,eAnGkB,SAAS,EAoG7B;AAAA,QAEJ,CAAC;AAAA;AAAA,IACH;AAAA,IAIF,oBAAC,UAAO,MAAM,mBAAmB,MAAM,cAAc,CAAC,SAAS;AAAE,UAAI,CAAC,KAAM,qBAAoB;AAAA,IAAE,GAChG,+BAAC,iBAAc,WAAW,cAAc,oBAAoB,GAC1D;AAAA,0BAAC,gBACC,8BAAC,eACE,0BAAgB,SAAS,WACtB,EAAE,0CAA0C,iBAAiB,IAC7D,EAAE,wCAAwC,eAAe,GAC/D,GACF;AAAA,MACA,qBAAC,SAAI,WAAU,kBACb;AAAA,6BAAC,SAAI,WAAU,aACb;AAAA,8BAAC,SAAM,SAAQ,iBAAiB,YAAE,iCAAiC,MAAM,GAAE;AAAA,UAC3E;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,OAAO,aAAa;AAAA,cACpB,UAAU,CAAC,MAAM,gBAAgB,CAAC,UAAU,EAAE,GAAG,MAAM,MAAM,EAAE,OAAO,MAAM,EAAE;AAAA,cAC9E,aAAa,EAAE,4CAA4C,mBAAmB;AAAA,cAC9E,WAAS;AAAA;AAAA,UACX;AAAA,WACF;AAAA,QACA,qBAAC,SAAI,WAAU,2BACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,SAAS,aAAa;AAAA,cACtB,iBAAiB,CAAC,YAAY,gBAAgB,CAAC,UAAU,EAAE,GAAG,MAAM,WAAW,YAAY,KAAK,EAAE;AAAA;AAAA,UACpG;AAAA,UACA,oBAAC,SAAM,SAAQ,oBAAmB,WAAU,kBACzC,YAAE,sCAAsC,yBAAyB,GACpE;AAAA,WACF;AAAA,SACF;AAAA,MACA,qBAAC,gBACC;AAAA,4BAAC,UAAO,SAAQ,WAAU,SAAS,qBAAqB,UAAU,oBAC/D,YAAE,qCAAqC,QAAQ,GAClD;AAAA,QACA,qBAAC,UAAO,SAAS,MAAM,KAAK,qBAAqB,GAAG,UAAU,sBAAsB,CAAC,aAAa,KAAK,KAAK,GACzG;AAAA,+BAAqB,oBAAC,WAAQ,WAAU,gBAAe,IAAK;AAAA,UAC5D,EAAE,mCAAmC,MAAM;AAAA,WAC9C;AAAA,SACF;AAAA,OACF,GACF;AAAA,IAEC;AAAA,IAGD,oBAAC,UAAO,MAAM,gBAAgB,MAAM,cAAc,CAAC,SAAS;AAAE,UAAI,CAAC,KAAM,kBAAiB;AAAA,IAAE,GAC1F,+BAAC,iBAAc,WAAW,cAAc,iBAAiB,GACvD;AAAA,0BAAC,gBACC,8BAAC,eACE,uBAAa,SAAS,WACnB,EAAE,+CAA+C,WAAW,IAC5D,EAAE,6CAA6C,YAAY,GACjE,GACF;AAAA,MACA,qBAAC,SAAI,WAAU,kBACb;AAAA,6BAAC,SAAI,WAAU,aACb;AAAA,8BAAC,SAAM,SAAQ,eAAe,YAAE,uCAAuC,OAAO,GAAE;AAAA,UAChF;AAAA,YAAC;AAAA;AAAA,cACC,IAAG;AAAA,cACH,OAAO,UAAU;AAAA,cACjB,UAAU,CAAC,MAAM,aAAa,CAAC,UAAU,EAAE,GAAG,MAAM,OAAO,EAAE,OAAO,MAAM,EAAE;AAAA,cAC5E,aAAa,EAAE,kDAAkD,gBAAgB;AAAA,cACjF,WAAS;AAAA;AAAA,UACX;AAAA,WACF;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,UAAU;AAAA,YACjB,MAAM,UAAU;AAAA,YAChB,eAAe,CAAC,SAAS,aAAa,CAAC,UAAU,EAAE,GAAG,MAAM,OAAO,KAAK,EAAE;AAAA,YAC1E,cAAc,CAAC,SAAS,aAAa,CAAC,UAAU,EAAE,GAAG,MAAM,MAAM,KAAK,EAAE;AAAA,YACxE,QAAQ;AAAA;AAAA,QACV;AAAA,SACF;AAAA,MACA,qBAAC,gBACC;AAAA,4BAAC,UAAO,SAAQ,WAAU,SAAS,kBAAkB,UAAU,iBAC5D,YAAE,0CAA0C,QAAQ,GACvD;AAAA,QACA,qBAAC,UAAO,SAAS,MAAM,KAAK,kBAAkB,GAAG,UAAU,mBAAmB,CAAC,UAAU,MAAM,KAAK,GACjG;AAAA,4BAAkB,oBAAC,WAAQ,WAAU,gBAAe,IAAK;AAAA,UACzD,EAAE,wCAAwC,MAAM;AAAA,WACnD;AAAA,SACF;AAAA,OACF,GACF;AAAA,KACF;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -7,11 +7,12 @@ import { isToday } from "date-fns/isToday";
|
|
|
7
7
|
import { isTomorrow } from "date-fns/isTomorrow";
|
|
8
8
|
import { startOfDay } from "date-fns/startOfDay";
|
|
9
9
|
import { CalendarClock } from "lucide-react";
|
|
10
|
-
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
10
|
+
import { useLocale, useT } from "@open-mercato/shared/lib/i18n/context";
|
|
11
11
|
import { cn } from "@open-mercato/shared/lib/utils";
|
|
12
12
|
import { Avatar } from "@open-mercato/ui/primitives/avatar";
|
|
13
13
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
14
14
|
import { EmptyState } from "@open-mercato/ui/primitives/empty-state";
|
|
15
|
+
import { eventDisplayTitle, pluralCategory } from "../../lib/calendar/labels.js";
|
|
15
16
|
const MAX_AVATARS_PER_ROW = 2;
|
|
16
17
|
const CATEGORY_BADGE_CLASS = {
|
|
17
18
|
meeting: "bg-status-info-bg text-status-info-text",
|
|
@@ -70,13 +71,17 @@ function buildDayGroups(anchor, horizonDays, items) {
|
|
|
70
71
|
}
|
|
71
72
|
function AgendaDayHeader({ date, count }) {
|
|
72
73
|
const t = useT();
|
|
74
|
+
const locale = useLocale();
|
|
73
75
|
const todayMarker = isToday(date);
|
|
74
76
|
const tomorrowMarker = !todayMarker && isTomorrow(date);
|
|
77
|
+
const countKey = `customers.calendar.agenda.eventsCount.${pluralCategory(locale, count)}`;
|
|
78
|
+
const resolvedCount = t(countKey, { count });
|
|
79
|
+
const countLabel = resolvedCount === countKey ? t("customers.calendar.agenda.eventsCount.other", "{count} events", { count }) : resolvedCount;
|
|
75
80
|
return /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-2 bg-muted/50 px-3 py-2.5 sm:px-5", children: [
|
|
76
81
|
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold text-foreground", children: groupLabelOf(date) }),
|
|
77
82
|
todayMarker || tomorrowMarker ? /* @__PURE__ */ jsx("span", { className: cn("text-xs font-medium", todayMarker ? "text-foreground" : "text-muted-foreground"), children: `\xB7 ${todayMarker ? t("customers.calendar.agenda.today", "Today") : t("customers.calendar.agenda.tomorrow", "Tomorrow")}` }) : null,
|
|
78
83
|
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "w-2.5 shrink-0" }),
|
|
79
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children:
|
|
84
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: countLabel })
|
|
80
85
|
] });
|
|
81
86
|
}
|
|
82
87
|
function AgendaRow({
|
|
@@ -87,6 +92,7 @@ function AgendaRow({
|
|
|
87
92
|
const t = useT();
|
|
88
93
|
const canceled = item.status === "canceled";
|
|
89
94
|
const done = item.status === "done";
|
|
95
|
+
const title = eventDisplayTitle(item.title, t("customers.calendar.grid.untitled", "Untitled"));
|
|
90
96
|
const typeLabel = typeLabels?.[item.interactionType] ?? deriveTypeLabel(item.interactionType);
|
|
91
97
|
const platformLabels = {
|
|
92
98
|
zoom: t("customers.calendar.platformFull.zoom", "Zoom"),
|
|
@@ -103,7 +109,7 @@ function AgendaRow({
|
|
|
103
109
|
{
|
|
104
110
|
type: "button",
|
|
105
111
|
variant: "ghost",
|
|
106
|
-
"aria-label": `${
|
|
112
|
+
"aria-label": `${title} \xB7 ${ariaTime}`,
|
|
107
113
|
onClick: () => onItemClick(item),
|
|
108
114
|
className: cn(
|
|
109
115
|
"h-auto w-full justify-start whitespace-normal rounded-none bg-background px-3 py-3 text-left transition-colors hover:bg-muted/30 sm:gap-3.5 sm:px-5",
|
|
@@ -131,7 +137,7 @@ function AgendaRow({
|
|
|
131
137
|
done || canceled ? "text-muted-foreground" : "text-foreground",
|
|
132
138
|
canceled && "line-through"
|
|
133
139
|
),
|
|
134
|
-
children:
|
|
140
|
+
children: title
|
|
135
141
|
}
|
|
136
142
|
),
|
|
137
143
|
/* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: locationLabel ? `${typeLabel} \xB7 ${locationLabel}` : typeLabel })
|