@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
|
@@ -5,6 +5,7 @@ import { Page, PageBody } from '@open-mercato/ui/backend/Page'
|
|
|
5
5
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
6
6
|
import { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
|
|
7
7
|
import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
|
|
8
|
+
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
8
9
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
9
10
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
10
11
|
import { Input } from '@open-mercato/ui/primitives/input'
|
|
@@ -66,6 +67,31 @@ export default function PipelineStagesPage() {
|
|
|
66
67
|
const [stageIcon, setStageIcon] = React.useState<string | null>(null)
|
|
67
68
|
const [saving, setSaving] = React.useState(false)
|
|
68
69
|
|
|
70
|
+
const { runMutation, retryLastMutation } = useGuardedMutation<{
|
|
71
|
+
formId: string
|
|
72
|
+
resourceKind: string
|
|
73
|
+
retryLastMutation: () => Promise<boolean>
|
|
74
|
+
}>({
|
|
75
|
+
contextId: 'customers-pipeline-stages-page',
|
|
76
|
+
blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),
|
|
77
|
+
})
|
|
78
|
+
const pipelineMutationContext = React.useMemo(
|
|
79
|
+
() => ({
|
|
80
|
+
formId: 'customers-pipeline-stages-page',
|
|
81
|
+
resourceKind: 'customers.pipeline',
|
|
82
|
+
retryLastMutation,
|
|
83
|
+
}),
|
|
84
|
+
[retryLastMutation],
|
|
85
|
+
)
|
|
86
|
+
const stageMutationContext = React.useMemo(
|
|
87
|
+
() => ({
|
|
88
|
+
formId: 'customers-pipeline-stages-page',
|
|
89
|
+
resourceKind: 'customers.pipeline_stage',
|
|
90
|
+
retryLastMutation,
|
|
91
|
+
}),
|
|
92
|
+
[retryLastMutation],
|
|
93
|
+
)
|
|
94
|
+
|
|
69
95
|
const selectedPipeline = React.useMemo(
|
|
70
96
|
() => pipelines.find((p) => p.id === selectedPipelineId) ?? null,
|
|
71
97
|
[pipelines, selectedPipelineId],
|
|
@@ -135,30 +161,54 @@ export default function PipelineStagesPage() {
|
|
|
135
161
|
setSaving(true)
|
|
136
162
|
try {
|
|
137
163
|
if (pipelineDialog?.mode === 'create') {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
164
|
+
let newId: string | null = null
|
|
165
|
+
try {
|
|
166
|
+
await runMutation({
|
|
167
|
+
operation: async () => {
|
|
168
|
+
const result = await apiCall<{ id: string }>('/api/customers/pipelines', {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
body: JSON.stringify({ name: pipelineName.trim(), isDefault: pipelineIsDefault }),
|
|
171
|
+
headers: { 'Content-Type': 'application/json' },
|
|
172
|
+
})
|
|
173
|
+
if (!result.ok) {
|
|
174
|
+
throw new Error(t('customers.config.pipelineStages.errorCreatePipeline', 'Failed to create pipeline'))
|
|
175
|
+
}
|
|
176
|
+
newId = result.result?.id ?? null
|
|
177
|
+
},
|
|
178
|
+
context: pipelineMutationContext,
|
|
179
|
+
mutationPayload: { action: 'create', name: pipelineName.trim(), isDefault: pipelineIsDefault },
|
|
180
|
+
})
|
|
181
|
+
} catch (err) {
|
|
182
|
+
const msg = err instanceof Error && err.message ? err.message : t('customers.config.pipelineStages.errorCreatePipeline', 'Failed to create pipeline')
|
|
183
|
+
flash(msg, 'error')
|
|
145
184
|
return
|
|
146
185
|
}
|
|
147
186
|
flash(t('customers.config.pipelineStages.createdPipeline', 'Pipeline created'), 'success')
|
|
148
|
-
const newId = result.result?.id ?? null
|
|
149
187
|
await loadPipelines()
|
|
150
188
|
if (newId) setSelectedPipelineId(newId)
|
|
151
189
|
} else if (pipelineDialog?.mode === 'edit') {
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
190
|
+
const targetPipeline = pipelineDialog.pipeline
|
|
191
|
+
try {
|
|
192
|
+
await runMutation({
|
|
193
|
+
operation: async () => {
|
|
194
|
+
const result = await withScopedApiRequestHeaders(
|
|
195
|
+
buildOptimisticLockHeader(targetPipeline.updatedAt),
|
|
196
|
+
() => apiCall('/api/customers/pipelines', {
|
|
197
|
+
method: 'PUT',
|
|
198
|
+
body: JSON.stringify({ id: targetPipeline.id, name: pipelineName.trim(), isDefault: pipelineIsDefault }),
|
|
199
|
+
headers: { 'Content-Type': 'application/json' },
|
|
200
|
+
}),
|
|
201
|
+
)
|
|
202
|
+
if (!result.ok) {
|
|
203
|
+
throw new Error(t('customers.config.pipelineStages.errorUpdatePipeline', 'Failed to update pipeline'))
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
context: pipelineMutationContext,
|
|
207
|
+
mutationPayload: { action: 'update', id: targetPipeline.id, name: pipelineName.trim(), isDefault: pipelineIsDefault },
|
|
208
|
+
})
|
|
209
|
+
} catch (err) {
|
|
210
|
+
const msg = err instanceof Error && err.message ? err.message : t('customers.config.pipelineStages.errorUpdatePipeline', 'Failed to update pipeline')
|
|
211
|
+
flash(msg, 'error')
|
|
162
212
|
return
|
|
163
213
|
}
|
|
164
214
|
flash(t('customers.config.pipelineStages.updatedPipeline', 'Pipeline updated'), 'success')
|
|
@@ -181,17 +231,28 @@ export default function PipelineStagesPage() {
|
|
|
181
231
|
variant: 'destructive',
|
|
182
232
|
})
|
|
183
233
|
if (!confirmed) return
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
234
|
+
try {
|
|
235
|
+
await runMutation({
|
|
236
|
+
operation: async () => {
|
|
237
|
+
const result = await withScopedApiRequestHeaders(
|
|
238
|
+
buildOptimisticLockHeader(pipeline.updatedAt),
|
|
239
|
+
() => apiCall('/api/customers/pipelines', {
|
|
240
|
+
method: 'DELETE',
|
|
241
|
+
body: JSON.stringify({ id: pipeline.id }),
|
|
242
|
+
headers: { 'Content-Type': 'application/json' },
|
|
243
|
+
}),
|
|
244
|
+
)
|
|
245
|
+
if (!result.ok) {
|
|
246
|
+
const error = (result.result as { error?: string })?.error
|
|
247
|
+
throw new Error(error ?? t('customers.config.pipelineStages.errorDeletePipeline', 'Failed to delete pipeline'))
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
context: pipelineMutationContext,
|
|
251
|
+
mutationPayload: { action: 'delete', id: pipeline.id },
|
|
252
|
+
})
|
|
253
|
+
} catch (err) {
|
|
254
|
+
const msg = err instanceof Error && err.message ? err.message : t('customers.config.pipelineStages.errorDeletePipeline', 'Failed to delete pipeline')
|
|
255
|
+
flash(msg, 'error')
|
|
195
256
|
return
|
|
196
257
|
}
|
|
197
258
|
flash(t('customers.config.pipelineStages.deletedPipeline', 'Pipeline deleted'), 'success')
|
|
@@ -222,27 +283,50 @@ export default function PipelineStagesPage() {
|
|
|
222
283
|
...(stageColor !== null ? { color: stageColor } : {}),
|
|
223
284
|
...(stageIcon !== null ? { icon: stageIcon } : {}),
|
|
224
285
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
286
|
+
try {
|
|
287
|
+
await runMutation({
|
|
288
|
+
operation: async () => {
|
|
289
|
+
const result = await apiCall('/api/customers/pipeline-stages', {
|
|
290
|
+
method: 'POST',
|
|
291
|
+
body: JSON.stringify({ pipelineId: selectedPipelineId, label: stageName.trim(), ...appearancePayload }),
|
|
292
|
+
headers: { 'Content-Type': 'application/json' },
|
|
293
|
+
})
|
|
294
|
+
if (!result.ok) {
|
|
295
|
+
throw new Error(t('customers.config.pipelineStages.errorCreateStage', 'Failed to create stage'))
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
context: stageMutationContext,
|
|
299
|
+
mutationPayload: { action: 'create', pipelineId: selectedPipelineId, label: stageName.trim(), ...appearancePayload },
|
|
300
|
+
})
|
|
301
|
+
} catch (err) {
|
|
302
|
+
const msg = err instanceof Error && err.message ? err.message : t('customers.config.pipelineStages.errorCreateStage', 'Failed to create stage')
|
|
303
|
+
flash(msg, 'error')
|
|
232
304
|
return
|
|
233
305
|
}
|
|
234
306
|
flash(t('customers.config.pipelineStages.createdStage', 'Stage created'), 'success')
|
|
235
307
|
} else if (stageDialog?.mode === 'edit') {
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
308
|
+
const targetStage = stageDialog.stage
|
|
309
|
+
try {
|
|
310
|
+
await runMutation({
|
|
311
|
+
operation: async () => {
|
|
312
|
+
const result = await withScopedApiRequestHeaders(
|
|
313
|
+
buildOptimisticLockHeader(targetStage.updatedAt),
|
|
314
|
+
() => apiCall('/api/customers/pipeline-stages', {
|
|
315
|
+
method: 'PUT',
|
|
316
|
+
body: JSON.stringify({ id: targetStage.id, label: stageName.trim(), color: stageColor, icon: stageIcon }),
|
|
317
|
+
headers: { 'Content-Type': 'application/json' },
|
|
318
|
+
}),
|
|
319
|
+
)
|
|
320
|
+
if (!result.ok) {
|
|
321
|
+
throw new Error(t('customers.config.pipelineStages.errorUpdateStage', 'Failed to update stage'))
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
context: stageMutationContext,
|
|
325
|
+
mutationPayload: { action: 'update', id: targetStage.id, label: stageName.trim(), color: stageColor, icon: stageIcon },
|
|
326
|
+
})
|
|
327
|
+
} catch (err) {
|
|
328
|
+
const msg = err instanceof Error && err.message ? err.message : t('customers.config.pipelineStages.errorUpdateStage', 'Failed to update stage')
|
|
329
|
+
flash(msg, 'error')
|
|
246
330
|
return
|
|
247
331
|
}
|
|
248
332
|
flash(t('customers.config.pipelineStages.updatedStage', 'Stage updated'), 'success')
|
|
@@ -265,17 +349,28 @@ export default function PipelineStagesPage() {
|
|
|
265
349
|
variant: 'destructive',
|
|
266
350
|
})
|
|
267
351
|
if (!confirmed) return
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
352
|
+
try {
|
|
353
|
+
await runMutation({
|
|
354
|
+
operation: async () => {
|
|
355
|
+
const result = await withScopedApiRequestHeaders(
|
|
356
|
+
buildOptimisticLockHeader(stage.updatedAt),
|
|
357
|
+
() => apiCall('/api/customers/pipeline-stages', {
|
|
358
|
+
method: 'DELETE',
|
|
359
|
+
body: JSON.stringify({ id: stage.id }),
|
|
360
|
+
headers: { 'Content-Type': 'application/json' },
|
|
361
|
+
}),
|
|
362
|
+
)
|
|
363
|
+
if (!result.ok) {
|
|
364
|
+
const error = (result.result as { error?: string })?.error
|
|
365
|
+
throw new Error(error ?? t('customers.config.pipelineStages.errorDeleteStage', 'Failed to delete stage'))
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
context: stageMutationContext,
|
|
369
|
+
mutationPayload: { action: 'delete', id: stage.id },
|
|
370
|
+
})
|
|
371
|
+
} catch (err) {
|
|
372
|
+
const msg = err instanceof Error && err.message ? err.message : t('customers.config.pipelineStages.errorDeleteStage', 'Failed to delete stage')
|
|
373
|
+
flash(msg, 'error')
|
|
279
374
|
return
|
|
280
375
|
}
|
|
281
376
|
flash(t('customers.config.pipelineStages.deletedStage', 'Stage deleted'), 'success')
|
|
@@ -293,13 +388,25 @@ export default function PipelineStagesPage() {
|
|
|
293
388
|
const updated = reordered.map((stage, i) => ({ ...stage, order: i }))
|
|
294
389
|
setStages(updated)
|
|
295
390
|
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
391
|
+
const orderedStages = updated.map((s) => ({ id: s.id, order: s.order }))
|
|
392
|
+
try {
|
|
393
|
+
await runMutation({
|
|
394
|
+
operation: async () => {
|
|
395
|
+
const result = await apiCall('/api/customers/pipeline-stages/reorder', {
|
|
396
|
+
method: 'POST',
|
|
397
|
+
body: JSON.stringify({ stages: orderedStages }),
|
|
398
|
+
headers: { 'Content-Type': 'application/json' },
|
|
399
|
+
})
|
|
400
|
+
if (!result.ok) {
|
|
401
|
+
throw new Error(t('customers.config.pipelineStages.errorReorder', 'Failed to reorder stages'))
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
context: stageMutationContext,
|
|
405
|
+
mutationPayload: { action: 'reorder', stages: orderedStages },
|
|
406
|
+
})
|
|
407
|
+
} catch (err) {
|
|
408
|
+
const msg = err instanceof Error && err.message ? err.message : t('customers.config.pipelineStages.errorReorder', 'Failed to reorder stages')
|
|
409
|
+
flash(msg, 'error')
|
|
303
410
|
if (selectedPipelineId) await loadStages(selectedPipelineId)
|
|
304
411
|
}
|
|
305
412
|
}
|
|
@@ -5,9 +5,12 @@ import { Spinner } from '@open-mercato/ui/primitives/spinner'
|
|
|
5
5
|
import { RadioGroup, Radio } from '@open-mercato/ui/primitives/radio'
|
|
6
6
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
7
7
|
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
8
|
+
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
8
9
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
9
10
|
import type { AddressFormatStrategy } from '../utils/addressFormat'
|
|
10
11
|
|
|
12
|
+
const SAVE_CONTEXT_ID = 'customers-address-format-settings'
|
|
13
|
+
|
|
11
14
|
type Option = {
|
|
12
15
|
id: AddressFormatStrategy
|
|
13
16
|
title: string
|
|
@@ -21,6 +24,15 @@ export function AddressFormatSettings() {
|
|
|
21
24
|
const [pending, setPending] = React.useState<AddressFormatStrategy | null>(null)
|
|
22
25
|
const [error, setError] = React.useState<string | null>(null)
|
|
23
26
|
|
|
27
|
+
const { runMutation, retryLastMutation } = useGuardedMutation<{
|
|
28
|
+
formId: string
|
|
29
|
+
resourceKind: string
|
|
30
|
+
retryLastMutation: () => Promise<boolean>
|
|
31
|
+
}>({
|
|
32
|
+
contextId: SAVE_CONTEXT_ID,
|
|
33
|
+
blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),
|
|
34
|
+
})
|
|
35
|
+
|
|
24
36
|
const options = React.useMemo<Option[]>(
|
|
25
37
|
() => [
|
|
26
38
|
{
|
|
@@ -87,25 +99,33 @@ export function AddressFormatSettings() {
|
|
|
87
99
|
setPending(next)
|
|
88
100
|
setError(null)
|
|
89
101
|
try {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
102
|
+
await runMutation({
|
|
103
|
+
// optimistic-lock-exempt: single-row tenant address-format preference toggle — no per-record version / concurrent record edit
|
|
104
|
+
operation: async () => {
|
|
105
|
+
const call = await apiCall<Record<string, unknown>>(
|
|
106
|
+
'/api/customers/settings/address-format',
|
|
107
|
+
{
|
|
108
|
+
method: 'PUT',
|
|
109
|
+
headers: { 'content-type': 'application/json' },
|
|
110
|
+
body: JSON.stringify({ addressFormat: next }),
|
|
111
|
+
},
|
|
112
|
+
)
|
|
113
|
+
const payload = call.result ?? {}
|
|
114
|
+
if (!call.ok) {
|
|
115
|
+
const message =
|
|
116
|
+
typeof payload?.error === 'string'
|
|
117
|
+
? payload.error
|
|
118
|
+
: t('customers.config.addressFormat.errorSave', 'Failed to update address settings')
|
|
119
|
+
throw new Error(message)
|
|
120
|
+
}
|
|
97
121
|
},
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
setError(message)
|
|
106
|
-
flash(message, 'error')
|
|
107
|
-
return
|
|
108
|
-
}
|
|
122
|
+
context: {
|
|
123
|
+
formId: SAVE_CONTEXT_ID,
|
|
124
|
+
resourceKind: 'customers.settings',
|
|
125
|
+
retryLastMutation,
|
|
126
|
+
},
|
|
127
|
+
mutationPayload: { addressFormat: next },
|
|
128
|
+
})
|
|
109
129
|
setFormat(next)
|
|
110
130
|
flash(t('customers.config.addressFormat.success', 'Address format updated'), 'success')
|
|
111
131
|
} catch (err) {
|
|
@@ -119,7 +139,7 @@ export function AddressFormatSettings() {
|
|
|
119
139
|
setPending(null)
|
|
120
140
|
}
|
|
121
141
|
},
|
|
122
|
-
[format, t]
|
|
142
|
+
[format, retryLastMutation, runMutation, t]
|
|
123
143
|
)
|
|
124
144
|
|
|
125
145
|
return (
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
11
11
|
import { apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
|
|
12
12
|
import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
|
|
13
|
+
import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
|
|
13
14
|
import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
|
|
14
15
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
15
16
|
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
@@ -128,6 +129,24 @@ function CustomerDictionarySection({ kind, title, description }: CustomerDiction
|
|
|
128
129
|
const [dialog, setDialog] = React.useState<DialogState | null>(null)
|
|
129
130
|
const [submitting, setSubmitting] = React.useState(false)
|
|
130
131
|
|
|
132
|
+
const saveContextId = `customers-dictionary-${kind}`
|
|
133
|
+
const { runMutation, retryLastMutation } = useGuardedMutation<{
|
|
134
|
+
formId: string
|
|
135
|
+
resourceKind: string
|
|
136
|
+
retryLastMutation: () => Promise<boolean>
|
|
137
|
+
}>({
|
|
138
|
+
contextId: saveContextId,
|
|
139
|
+
blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),
|
|
140
|
+
})
|
|
141
|
+
const mutationContext = React.useMemo(
|
|
142
|
+
() => ({
|
|
143
|
+
formId: saveContextId,
|
|
144
|
+
resourceKind: 'customers.dictionary',
|
|
145
|
+
retryLastMutation,
|
|
146
|
+
}),
|
|
147
|
+
[retryLastMutation, saveContextId],
|
|
148
|
+
)
|
|
149
|
+
|
|
131
150
|
const inheritedActionBlocked = t('customers.config.dictionaries.inherited.blocked', 'Inherited entries can only be edited from the parent organization.')
|
|
132
151
|
const inheritedTooltip = t('customers.config.dictionaries.inherited.tooltip', 'Managed in parent organization')
|
|
133
152
|
const inheritedLabel = t('customers.config.dictionaries.inherited.label', 'Inherited')
|
|
@@ -221,13 +240,18 @@ function CustomerDictionarySection({ kind, title, description }: CustomerDiction
|
|
|
221
240
|
})
|
|
222
241
|
if (!confirmed) return
|
|
223
242
|
try {
|
|
224
|
-
await
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
243
|
+
await runMutation({
|
|
244
|
+
operation: () =>
|
|
245
|
+
withScopedApiRequestHeaders(buildOptimisticLockHeader(entry.updatedAt), () =>
|
|
246
|
+
apiCallOrThrow(
|
|
247
|
+
`/api/customers/dictionaries/${kind}/${encodeURIComponent(entry.id)}`,
|
|
248
|
+
{ method: 'DELETE' },
|
|
249
|
+
{ errorMessage: errorDelete },
|
|
250
|
+
),
|
|
251
|
+
),
|
|
252
|
+
context: mutationContext,
|
|
253
|
+
mutationPayload: { action: 'delete', id: entry.id, kind },
|
|
254
|
+
})
|
|
231
255
|
flash(successDelete, 'success')
|
|
232
256
|
await loadEntries()
|
|
233
257
|
} catch (err) {
|
|
@@ -245,7 +269,7 @@ function CustomerDictionarySection({ kind, title, description }: CustomerDiction
|
|
|
245
269
|
const messageValue = err instanceof Error ? err.message : errorDelete
|
|
246
270
|
flash(messageValue, 'error')
|
|
247
271
|
}
|
|
248
|
-
}, [confirm, deleteConfirmTemplate, errorDelete, formatCountMessage, inheritedActionBlocked, kind, loadEntries, roleTypeDeleteBlockedText, roleTypeDeleteBlockedTitle, successDelete, t])
|
|
272
|
+
}, [confirm, deleteConfirmTemplate, errorDelete, formatCountMessage, inheritedActionBlocked, kind, loadEntries, mutationContext, roleTypeDeleteBlockedText, roleTypeDeleteBlockedTitle, runMutation, successDelete, t])
|
|
249
273
|
|
|
250
274
|
const submitForm = React.useCallback(async (values: DictionaryFormValues) => {
|
|
251
275
|
const payload = {
|
|
@@ -257,15 +281,20 @@ function CustomerDictionarySection({ kind, title, description }: CustomerDiction
|
|
|
257
281
|
setSubmitting(true)
|
|
258
282
|
try {
|
|
259
283
|
if (!dialog || dialog.mode === 'create') {
|
|
260
|
-
await
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
284
|
+
await runMutation({
|
|
285
|
+
operation: () =>
|
|
286
|
+
apiCallOrThrow(
|
|
287
|
+
`/api/customers/dictionaries/${kind}`,
|
|
288
|
+
{
|
|
289
|
+
method: 'POST',
|
|
290
|
+
headers: { 'content-type': 'application/json' },
|
|
291
|
+
body: JSON.stringify(payload),
|
|
292
|
+
},
|
|
293
|
+
{ errorMessage: errorSave },
|
|
294
|
+
),
|
|
295
|
+
context: mutationContext,
|
|
296
|
+
mutationPayload: { action: 'create', kind, ...payload },
|
|
297
|
+
})
|
|
269
298
|
flash(successSave, 'success')
|
|
270
299
|
} else if (dialog.mode === 'edit') {
|
|
271
300
|
const target = dialog.entry
|
|
@@ -284,17 +313,22 @@ function CustomerDictionarySection({ kind, title, description }: CustomerDiction
|
|
|
284
313
|
closeDialog()
|
|
285
314
|
return
|
|
286
315
|
}
|
|
287
|
-
await
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
316
|
+
await runMutation({
|
|
317
|
+
operation: () =>
|
|
318
|
+
withScopedApiRequestHeaders(buildOptimisticLockHeader(target.updatedAt), () =>
|
|
319
|
+
apiCallOrThrow(
|
|
320
|
+
`/api/customers/dictionaries/${kind}/${encodeURIComponent(target.id)}`,
|
|
321
|
+
{
|
|
322
|
+
method: 'PATCH',
|
|
323
|
+
headers: { 'content-type': 'application/json' },
|
|
324
|
+
body: JSON.stringify(body),
|
|
325
|
+
},
|
|
326
|
+
{ errorMessage: errorSave },
|
|
327
|
+
),
|
|
328
|
+
),
|
|
329
|
+
context: mutationContext,
|
|
330
|
+
mutationPayload: { action: 'update', id: target.id, kind, ...body },
|
|
331
|
+
})
|
|
298
332
|
flash(successSave, 'success')
|
|
299
333
|
}
|
|
300
334
|
closeDialog()
|
|
@@ -305,7 +339,7 @@ function CustomerDictionarySection({ kind, title, description }: CustomerDiction
|
|
|
305
339
|
} finally {
|
|
306
340
|
setSubmitting(false)
|
|
307
341
|
}
|
|
308
|
-
}, [closeDialog, dialog, errorSave, inheritedActionBlocked, kind, loadEntries, successSave])
|
|
342
|
+
}, [closeDialog, dialog, errorSave, inheritedActionBlocked, kind, loadEntries, mutationContext, runMutation, successSave])
|
|
309
343
|
|
|
310
344
|
const currentValues = React.useMemo<DictionaryFormValues>(() => {
|
|
311
345
|
if (dialog && dialog.mode === 'edit') {
|