@open-mercato/core 0.5.1-develop.2953.6647bb2c43 → 0.5.1-develop.2964.d5ac4a6ebb
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/dist/helpers/integration/salesUi.js +25 -23
- package/dist/helpers/integration/salesUi.js.map +2 -2
- package/dist/modules/api_docs/frontend/docs/api/Explorer.js +24 -24
- package/dist/modules/api_docs/frontend/docs/api/Explorer.js.map +2 -2
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js +15 -7
- package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +2 -2
- package/dist/modules/attachments/fields/attachment.js +4 -6
- package/dist/modules/attachments/fields/attachment.js.map +2 -2
- package/dist/modules/auth/backend/users/create/page.js +26 -26
- package/dist/modules/auth/backend/users/create/page.js.map +2 -2
- package/dist/modules/business_rules/components/ActionRow.js +36 -25
- package/dist/modules/business_rules/components/ActionRow.js.map +2 -2
- package/dist/modules/business_rules/components/ConditionGroup.js +14 -5
- package/dist/modules/business_rules/components/ConditionGroup.js.map +2 -2
- package/dist/modules/business_rules/components/ConditionRow.js +19 -10
- package/dist/modules/business_rules/components/ConditionRow.js.map +2 -2
- package/dist/modules/business_rules/components/RuleSetMembers.js +16 -10
- package/dist/modules/business_rules/components/RuleSetMembers.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +30 -34
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/create/page.js +220 -223
- package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
- package/dist/modules/catalog/components/PriceKindSettings.js +20 -19
- package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductUomSection.js +42 -37
- package/dist/modules/catalog/components/products/ProductUomSection.js.map +2 -2
- package/dist/modules/catalog/components/products/VariantBuilder.js +22 -18
- package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +18 -26
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +4 -6
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +5 -4
- 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 +19 -7
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +24 -21
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/components/AddressEditor.js +24 -7
- package/dist/modules/customers/components/AddressEditor.js.map +2 -2
- package/dist/modules/customers/components/AddressFormatSettings.js +35 -25
- package/dist/modules/customers/components/AddressFormatSettings.js.map +2 -2
- package/dist/modules/customers/components/detail/ActivityForm.js +20 -12
- package/dist/modules/customers/components/detail/ActivityForm.js.map +2 -2
- package/dist/modules/customers/components/detail/AnnualRevenueField.js +2 -2
- package/dist/modules/customers/components/detail/AnnualRevenueField.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +19 -14
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/formConfig.js +16 -12
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/customers/widgets/dashboard/customer-todos/widget.client.js +3 -2
- package/dist/modules/customers/widgets/dashboard/customer-todos/widget.client.js.map +2 -2
- package/dist/modules/customers/widgets/dashboard/new-customers/widget.client.js +18 -10
- package/dist/modules/customers/widgets/dashboard/new-customers/widget.client.js.map +2 -2
- package/dist/modules/customers/widgets/dashboard/new-deals/widget.client.js +3 -2
- package/dist/modules/customers/widgets/dashboard/new-deals/widget.client.js.map +2 -2
- package/dist/modules/customers/widgets/dashboard/next-interactions/widget.client.js +3 -2
- package/dist/modules/customers/widgets/dashboard/next-interactions/widget.client.js.map +2 -2
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +27 -28
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js +14 -6
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js +14 -6
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js +3 -2
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js +3 -2
- package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js +17 -8
- package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js.map +2 -2
- package/dist/modules/data_sync/backend/data-sync/page.js +40 -23
- package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js +15 -6
- package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
- package/dist/modules/dictionaries/components/AppearanceSelector.js +4 -4
- package/dist/modules/dictionaries/components/AppearanceSelector.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js +4 -5
- package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionaryEntrySelect.js +22 -14
- package/dist/modules/dictionaries/components/DictionaryEntrySelect.js.map +2 -2
- package/dist/modules/dictionaries/fields/dictionary.js +18 -13
- package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
- package/dist/modules/entities/components/EncryptionManager.js +23 -19
- package/dist/modules/entities/components/EncryptionManager.js.map +2 -2
- package/dist/modules/feature_toggles/components/formConfig.js +17 -9
- package/dist/modules/feature_toggles/components/formConfig.js.map +2 -2
- package/dist/modules/feature_toggles/components/overrideFormConfig.js +17 -9
- package/dist/modules/feature_toggles/components/overrideFormConfig.js.map +2 -2
- package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js +15 -8
- package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js.map +2 -2
- package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js +37 -22
- package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +22 -17
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +12 -6
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +19 -12
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/resources/components/ResourceCrudForm.js +15 -10
- package/dist/modules/resources/components/ResourceCrudForm.js.map +3 -3
- package/dist/modules/sales/backend/sales/documents/[id]/page.js +15 -18
- package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
- package/dist/modules/sales/components/ProviderFieldInput.js +23 -20
- package/dist/modules/sales/components/ProviderFieldInput.js.map +2 -2
- package/dist/modules/sales/components/ShippingMethodsSettings.js +25 -17
- package/dist/modules/sales/components/ShippingMethodsSettings.js.map +3 -3
- package/dist/modules/sales/components/channels/ChannelOfferForm.js +35 -42
- package/dist/modules/sales/components/channels/ChannelOfferForm.js.map +2 -2
- package/dist/modules/sales/components/documents/AddressesSection.js +87 -90
- package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
- package/dist/modules/sales/components/documents/AdjustmentDialog.js +17 -6
- package/dist/modules/sales/components/documents/AdjustmentDialog.js.map +3 -3
- package/dist/modules/sales/components/documents/LineItemDialog.js +42 -25
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentForm.js +96 -87
- package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
- package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js +20 -11
- package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js.map +2 -2
- package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js +20 -11
- package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.js +36 -22
- package/dist/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.js.map +2 -2
- package/dist/modules/staff/components/TeamMemberForm.js +14 -9
- package/dist/modules/staff/components/TeamMemberForm.js.map +3 -3
- package/dist/modules/workflows/backend/tasks/[id]/page.js +42 -21
- package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
- package/dist/modules/workflows/components/ActivitiesEditor.js +14 -6
- package/dist/modules/workflows/components/ActivitiesEditor.js.map +3 -3
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js +25 -17
- package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +3 -3
- package/dist/modules/workflows/components/EdgeEditDialog.js +48 -45
- package/dist/modules/workflows/components/EdgeEditDialog.js.map +2 -2
- package/dist/modules/workflows/components/NodeEditDialog.js +90 -90
- package/dist/modules/workflows/components/NodeEditDialog.js.map +2 -2
- package/dist/modules/workflows/components/StepsEditor.js +14 -6
- package/dist/modules/workflows/components/StepsEditor.js.map +3 -3
- package/dist/modules/workflows/components/TransitionsEditor.js +31 -26
- package/dist/modules/workflows/components/TransitionsEditor.js.map +3 -3
- package/dist/modules/workflows/components/fields/ActivityArrayEditor.js +19 -11
- package/dist/modules/workflows/components/fields/ActivityArrayEditor.js.map +3 -3
- package/dist/modules/workflows/components/fields/BusinessRuleConditionsEditor.js +12 -14
- package/dist/modules/workflows/components/fields/BusinessRuleConditionsEditor.js.map +2 -2
- package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js +24 -16
- package/dist/modules/workflows/components/fields/FormFieldArrayEditor.js.map +3 -3
- package/dist/modules/workflows/components/fields/StartPreConditionsEditor.js +12 -13
- package/dist/modules/workflows/components/fields/StartPreConditionsEditor.js.map +2 -2
- package/dist/modules/workflows/components/mobile/MobileTaskForm.js +12 -8
- package/dist/modules/workflows/components/mobile/MobileTaskForm.js.map +2 -2
- package/dist/modules/workflows/frontend/checkout-demo/page.js +43 -46
- package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
- package/package.json +3 -3
- package/src/helpers/integration/salesUi.ts +40 -30
- package/src/modules/api_docs/frontend/docs/api/Explorer.tsx +25 -19
- package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +21 -11
- package/src/modules/attachments/fields/attachment.tsx +4 -6
- package/src/modules/auth/backend/users/create/page.tsx +16 -20
- package/src/modules/business_rules/components/ActionRow.tsx +51 -32
- package/src/modules/business_rules/components/ConditionGroup.tsx +20 -9
- package/src/modules/business_rules/components/ConditionRow.tsx +24 -15
- package/src/modules/business_rules/components/RuleSetMembers.tsx +23 -13
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +47 -53
- package/src/modules/catalog/backend/catalog/products/create/page.tsx +84 -87
- package/src/modules/catalog/components/PriceKindSettings.tsx +9 -9
- package/src/modules/catalog/components/products/ProductUomSection.tsx +85 -83
- package/src/modules/catalog/components/products/VariantBuilder.tsx +49 -33
- package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +12 -27
- package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +4 -6
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +5 -4
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +28 -15
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +37 -26
- package/src/modules/customers/components/AddressEditor.tsx +30 -16
- package/src/modules/customers/components/AddressFormatSettings.tsx +25 -19
- package/src/modules/customers/components/detail/ActivityForm.tsx +35 -23
- package/src/modules/customers/components/detail/AnnualRevenueField.tsx +2 -2
- package/src/modules/customers/components/detail/DealForm.tsx +33 -20
- package/src/modules/customers/components/formConfig.tsx +25 -17
- package/src/modules/customers/widgets/dashboard/customer-todos/widget.client.tsx +3 -2
- package/src/modules/customers/widgets/dashboard/new-customers/widget.client.tsx +21 -11
- package/src/modules/customers/widgets/dashboard/new-deals/widget.client.tsx +3 -2
- package/src/modules/customers/widgets/dashboard/next-interactions/widget.client.tsx +3 -2
- package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +17 -22
- package/src/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.tsx +17 -7
- package/src/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.tsx +20 -10
- package/src/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.tsx +3 -2
- package/src/modules/dashboards/widgets/dashboard/top-customers/widget.client.tsx +3 -2
- package/src/modules/dashboards/widgets/dashboard/top-products/widget.client.tsx +20 -9
- package/src/modules/data_sync/backend/data-sync/page.tsx +64 -38
- package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +18 -7
- package/src/modules/dictionaries/components/AppearanceSelector.tsx +4 -4
- package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +3 -4
- package/src/modules/dictionaries/components/DictionaryEntrySelect.tsx +27 -21
- package/src/modules/dictionaries/fields/dictionary.tsx +36 -23
- package/src/modules/entities/components/EncryptionManager.tsx +49 -33
- package/src/modules/feature_toggles/components/formConfig.tsx +20 -10
- package/src/modules/feature_toggles/components/overrideFormConfig.tsx +20 -10
- package/src/modules/inbox_ops/backend/inbox-ops/settings/page.tsx +19 -10
- package/src/modules/inbox_ops/components/proposals/EditActionDialog.tsx +49 -26
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +20 -11
- package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +19 -9
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +34 -21
- package/src/modules/resources/components/ResourceCrudForm.tsx +24 -15
- package/src/modules/sales/backend/sales/documents/[id]/page.tsx +12 -15
- package/src/modules/sales/components/ProviderFieldInput.tsx +26 -17
- package/src/modules/sales/components/ShippingMethodsSettings.tsx +28 -20
- package/src/modules/sales/components/channels/ChannelOfferForm.tsx +51 -46
- package/src/modules/sales/components/documents/AddressesSection.tsx +78 -76
- package/src/modules/sales/components/documents/AdjustmentDialog.tsx +27 -15
- package/src/modules/sales/components/documents/LineItemDialog.tsx +69 -51
- package/src/modules/sales/components/documents/SalesDocumentForm.tsx +98 -87
- package/src/modules/sales/widgets/dashboard/new-orders/widget.client.tsx +23 -12
- package/src/modules/sales/widgets/dashboard/new-quotes/widget.client.tsx +23 -12
- package/src/modules/shipping_carriers/lib/shipment-wizard/components/ConfigureStep.tsx +35 -19
- package/src/modules/staff/components/TeamMemberForm.tsx +23 -14
- package/src/modules/workflows/backend/tasks/[id]/page.tsx +51 -23
- package/src/modules/workflows/components/ActivitiesEditor.tsx +20 -10
- package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +28 -18
- package/src/modules/workflows/components/EdgeEditDialog.tsx +51 -40
- package/src/modules/workflows/components/NodeEditDialog.tsx +81 -77
- package/src/modules/workflows/components/StepsEditor.tsx +20 -10
- package/src/modules/workflows/components/TransitionsEditor.tsx +61 -44
- package/src/modules/workflows/components/fields/ActivityArrayEditor.tsx +22 -12
- package/src/modules/workflows/components/fields/BusinessRuleConditionsEditor.tsx +9 -13
- package/src/modules/workflows/components/fields/FormFieldArrayEditor.tsx +27 -17
- package/src/modules/workflows/components/fields/StartPreConditionsEditor.tsx +9 -12
- package/src/modules/workflows/components/mobile/MobileTaskForm.tsx +19 -11
- package/src/modules/workflows/frontend/checkout-demo/page.tsx +71 -60
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/customers/backend/customers/deals/pipeline/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { ErrorNotice } from '@open-mercato/ui/primitives/ErrorNotice'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\n\ntype DealAssociation = { id: string; label: string }\n\ntype DealRecord = {\n id: string\n title: string\n status: string | null\n pipelineStage: string | null\n pipelineId: string | null\n pipelineStageId: string | null\n valueAmount: number | null\n valueCurrency: string | null\n probability: number | null\n expectedCloseAt: string | null\n expectedCloseAtTs: number | null\n createdAt: string | null\n createdAtTs: number | null\n updatedAt: string | null\n people: DealAssociation[]\n companies: DealAssociation[]\n}\n\ntype DealsQueryData = {\n deals: DealRecord[]\n total: number\n}\n\ntype StageDefinition = {\n id: string\n value: string | null\n label: string\n color: string | null\n icon: string | null\n}\n\ntype SortOption = 'probability' | 'createdAt' | 'expectedCloseAt'\n\ntype PipelineRecord = { id: string; name: string; isDefault: boolean }\ntype PipelineStageRecord = { id: string; label: string; order: number; pipelineId: string }\n\nconst DEALS_QUERY_LIMIT = 100\n\nconst dealsQueryKey = (scopeVersion: number, pipelineId: string | null) =>\n ['customers', 'deals', 'pipeline', `scope:${scopeVersion}`, `pipeline:${pipelineId ?? 'none'}`] as const\n\nconst sortOptions: SortOption[] = ['probability', 'createdAt', 'expectedCloseAt']\n\nfunction normalizeAmount(value: unknown): number | null {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return null\n const parsed = Number(trimmed)\n return Number.isFinite(parsed) ? parsed : null\n }\n return null\n}\n\nfunction normalizeProbability(value: unknown): number | null {\n const parsed = normalizeAmount(value)\n if (parsed === null) return null\n if (parsed < 0) return 0\n if (parsed > 100) return 100\n return Math.round(parsed)\n}\n\nfunction normalizeTimestamp(value: unknown): { iso: string | null; ts: number | null } {\n if (typeof value !== 'string' || !value.trim().length) return { iso: null, ts: null }\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return { iso: null, ts: null }\n return { iso: date.toISOString(), ts: date.getTime() }\n}\n\nfunction buildStageDefinitionsFromPipelineStages(\n pipelineStages: PipelineStageRecord[],\n deals: DealRecord[],\n t: ReturnType<typeof useT>,\n): StageDefinition[] {\n const result: StageDefinition[] = pipelineStages\n .slice()\n .sort((a, b) => a.order - b.order)\n .map((stage) => ({\n id: stage.id,\n value: stage.id,\n label: stage.label,\n color: null,\n icon: null,\n }))\n\n const knownIds = new Set(pipelineStages.map((s) => s.id))\n const hasUnassigned = deals.some((deal) => !deal.pipelineStageId || !knownIds.has(deal.pipelineStageId))\n if (hasUnassigned) {\n result.push({\n id: 'stage:__unassigned',\n value: null,\n label: translateWithFallback(t, 'customers.deals.pipeline.unassigned', 'No stage'),\n color: null,\n icon: null,\n })\n }\n\n return result\n}\n\nfunction createDealMap(deals: DealRecord[]): Map<string, DealRecord> {\n return deals.reduce<Map<string, DealRecord>>((acc, deal) => acc.set(deal.id, deal), new Map())\n}\n\nfunction groupDealsByStageId(deals: DealRecord[]): Map<string | null, DealRecord[]> {\n const byStage = new Map<string | null, DealRecord[]>()\n deals.forEach((deal) => {\n const stageKey = deal.pipelineStageId ?? null\n const bucket = byStage.get(stageKey) ?? []\n bucket.push(deal)\n byStage.set(stageKey, bucket)\n })\n return byStage\n}\n\nfunction sortDeals(deals: DealRecord[], option: SortOption): DealRecord[] {\n const sorted = [...deals]\n sorted.sort((a, b) => {\n if (option === 'probability') {\n const ap = typeof a.probability === 'number' ? a.probability : -1\n const bp = typeof b.probability === 'number' ? b.probability : -1\n if (ap !== bp) return bp - ap\n }\n if (option === 'expectedCloseAt') {\n const at = typeof a.expectedCloseAtTs === 'number' ? a.expectedCloseAtTs : Number.POSITIVE_INFINITY\n const bt = typeof b.expectedCloseAtTs === 'number' ? b.expectedCloseAtTs : Number.POSITIVE_INFINITY\n if (at !== bt) return at - bt\n }\n const at = typeof a.createdAtTs === 'number' ? a.createdAtTs : Number.NEGATIVE_INFINITY\n const bt = typeof b.createdAtTs === 'number' ? b.createdAtTs : Number.NEGATIVE_INFINITY\n if (option === 'createdAt') {\n if (at !== bt) return bt - at\n } else if (option === 'expectedCloseAt' || option === 'probability') {\n if (at !== bt) return bt - at\n }\n return a.title.localeCompare(b.title, undefined, { sensitivity: 'base' })\n })\n return sorted\n}\n\nfunction formatCurrency(amount: number | null, currency: string | null, fallback: string): string {\n if (amount === null || Number.isNaN(amount)) return fallback\n const code = currency && currency.length === 3 ? currency.toUpperCase() : 'USD'\n try {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency: code,\n maximumFractionDigits: 2,\n }).format(amount)\n } catch {\n return `${code} ${amount.toFixed(2)}`\n }\n}\n\nfunction formatProbability(probability: number | null, fallback: string): string {\n if (typeof probability !== 'number' || Number.isNaN(probability)) return fallback\n return `${probability}%`\n}\n\nexport default function SalesPipelinePage(): React.ReactElement {\n const t = useT()\n const translate = React.useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>) => {\n const value = translateWithFallback(t, key, fallback, params)\n if (value === fallback && params) {\n return fallback.replace(/\\{\\{(\\w+)\\}\\}|\\{(\\w+)\\}/g, (match, doubleToken, singleToken) => {\n const token = (doubleToken ?? singleToken) as string | undefined\n if (!token) return match\n const replacement = params[token]\n if (replacement === undefined) {\n return doubleToken ? `{{${token}}}` : `{${token}}`\n }\n return String(replacement)\n })\n }\n return value\n },\n [t],\n )\n const scopeVersion = useOrganizationScopeVersion()\n const queryClient = useQueryClient()\n const [sortBy, setSortBy] = React.useState<SortOption>('probability')\n const [pendingDealId, setPendingDealId] = React.useState<string | null>(null)\n const [selectedPipelineId, setSelectedPipelineId] = React.useState<string | null>(null)\n\n const pipelinesQuery = useQuery<PipelineRecord[]>({\n queryKey: ['customers', 'pipelines', `scope:${scopeVersion}`],\n staleTime: 60_000,\n queryFn: async () => {\n const payload = await readApiResultOrThrow<{ items: PipelineRecord[] }>(\n '/api/customers/pipelines',\n undefined,\n { errorMessage: translate('customers.deals.pipeline.loadError', 'Failed to load pipelines.') },\n )\n return payload?.items ?? []\n },\n })\n\n React.useEffect(() => {\n if (selectedPipelineId) return\n const pipelines = pipelinesQuery.data\n if (!pipelines || !pipelines.length) return\n const defaultPipeline = pipelines.find((p) => p.isDefault) ?? pipelines[0]\n if (defaultPipeline) setSelectedPipelineId(defaultPipeline.id)\n }, [pipelinesQuery.data, selectedPipelineId])\n\n const stagesQuery = useQuery<PipelineStageRecord[]>({\n queryKey: ['customers', 'pipeline-stages', `scope:${scopeVersion}`, `pipeline:${selectedPipelineId}`],\n enabled: !!selectedPipelineId,\n staleTime: 30_000,\n queryFn: async () => {\n const payload = await readApiResultOrThrow<{ items: PipelineStageRecord[] }>(\n `/api/customers/pipeline-stages?pipelineId=${encodeURIComponent(selectedPipelineId!)}`,\n undefined,\n { errorMessage: translate('customers.deals.pipeline.loadError', 'Failed to load stages.') },\n )\n return payload?.items ?? []\n },\n })\n\n const dealsKey = React.useMemo(() => dealsQueryKey(scopeVersion, selectedPipelineId), [scopeVersion, selectedPipelineId])\n\n const dealsQuery = useQuery<DealsQueryData>({\n queryKey: dealsKey,\n enabled: !!selectedPipelineId,\n staleTime: 30_000,\n queryFn: async () => {\n const search = new URLSearchParams()\n search.set('page', '1')\n search.set('pageSize', String(DEALS_QUERY_LIMIT))\n search.set('sortField', 'createdAt')\n search.set('sortDir', 'desc')\n if (selectedPipelineId) search.set('pipelineId', selectedPipelineId)\n const payload = await readApiResultOrThrow<Record<string, unknown>>(\n `/api/customers/deals?${search.toString()}`,\n undefined,\n { errorMessage: translate('customers.deals.pipeline.loadError', 'Failed to load deals.') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n const deals: DealRecord[] = []\n items.forEach((item) => {\n if (!item || typeof item !== 'object') return\n const data = item as Record<string, unknown>\n const id = typeof data.id === 'string' ? data.id : null\n if (!id) return\n const title =\n typeof data.title === 'string' && data.title.trim().length\n ? data.title.trim()\n : translate('customers.deals.pipeline.untitled', 'Untitled deal')\n const status =\n typeof data.status === 'string' && data.status.trim().length ? data.status.trim() : null\n const stage =\n typeof data.pipeline_stage === 'string' && data.pipeline_stage.trim().length\n ? data.pipeline_stage.trim()\n : null\n const amount = normalizeAmount(data.value_amount)\n const currency =\n typeof data.value_currency === 'string' && data.value_currency.trim().length\n ? data.value_currency.trim().toUpperCase()\n : null\n const probability = normalizeProbability(data.probability)\n const expected = normalizeTimestamp(data.expected_close_at)\n const created = normalizeTimestamp(data.created_at)\n const updated = normalizeTimestamp(data.updated_at)\n const rawPeople = Array.isArray(data.people) ? data.people : []\n const people: DealAssociation[] = rawPeople\n .map((entry) => {\n if (!entry || typeof entry !== 'object') return null\n const ref = entry as Record<string, unknown>\n const personId = typeof ref.id === 'string' ? ref.id : null\n if (!personId) return null\n const label =\n typeof ref.label === 'string' && ref.label.trim().length\n ? ref.label.trim()\n : personId\n return { id: personId, label }\n })\n .filter((entry): entry is DealAssociation => !!entry)\n const rawCompanies = Array.isArray(data.companies) ? data.companies : []\n const companies: DealAssociation[] = rawCompanies\n .map((entry) => {\n if (!entry || typeof entry !== 'object') return null\n const ref = entry as Record<string, unknown>\n const companyId = typeof ref.id === 'string' ? ref.id : null\n if (!companyId) return null\n const label =\n typeof ref.label === 'string' && ref.label.trim().length\n ? ref.label.trim()\n : companyId\n return { id: companyId, label }\n })\n .filter((entry): entry is DealAssociation => !!entry)\n deals.push({\n id,\n title,\n status,\n pipelineStage: stage,\n pipelineId: typeof data.pipeline_id === 'string' ? data.pipeline_id : null,\n pipelineStageId: typeof data.pipeline_stage_id === 'string' ? data.pipeline_stage_id : null,\n valueAmount: amount,\n valueCurrency: currency,\n probability,\n expectedCloseAt: expected.iso,\n expectedCloseAtTs: expected.ts,\n createdAt: created.iso,\n createdAtTs: created.ts,\n updatedAt: updated.iso,\n people,\n companies,\n })\n })\n\n const total = typeof payload?.total === 'number' ? payload.total : deals.length\n return { deals, total }\n },\n })\n\n const deals = dealsQuery.data?.deals ?? []\n const total = dealsQuery.data?.total ?? deals.length\n const dealMap = React.useMemo(() => createDealMap(deals), [deals])\n const groupedDeals = React.useMemo(() => groupDealsByStageId(deals), [deals])\n const stages = React.useMemo(\n () => buildStageDefinitionsFromPipelineStages(stagesQuery.data ?? [], deals, t),\n [stagesQuery.data, deals, t],\n )\n\n const dateFormatter = React.useMemo(\n () =>\n new Intl.DateTimeFormat(undefined, {\n dateStyle: 'medium',\n }),\n [],\n )\n\n const updateStageMutation = useMutation({\n mutationFn: async ({ id, pipelineStageId }: { id: string; pipelineStageId: string }) => {\n await apiCallOrThrow(\n '/api/customers/deals',\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id, pipelineStageId }),\n },\n { errorMessage: translate('customers.deals.pipeline.moveError', 'Failed to update deal stage.') },\n )\n return { id, pipelineStageId }\n },\n onMutate: async ({ id, pipelineStageId }) => {\n setPendingDealId(id)\n await queryClient.cancelQueries({ queryKey: dealsKey })\n const previous = queryClient.getQueryData<DealsQueryData>(dealsKey)\n if (previous) {\n const nextDeals = previous.deals.map((deal) =>\n deal.id === id ? { ...deal, pipelineStageId } : deal,\n )\n queryClient.setQueryData<DealsQueryData>(dealsKey, { ...previous, deals: nextDeals })\n }\n return { previous }\n },\n onError: (error, _variables, context) => {\n if (context?.previous) {\n queryClient.setQueryData<DealsQueryData>(dealsKey, context.previous)\n }\n const message =\n error instanceof Error && error.message\n ? error.message\n : translate('customers.deals.pipeline.moveError', 'Failed to update deal stage.')\n flash(message, 'error')\n },\n onSuccess: () => {\n flash(translate('customers.deals.pipeline.moveSuccess', 'Deal updated.'), 'success')\n },\n onSettled: () => {\n setPendingDealId(null)\n queryClient.invalidateQueries({ queryKey: dealsKey }).catch(() => {})\n },\n })\n\n const [draggingId, setDraggingId] = React.useState<string | null>(null)\n const [activeLane, setActiveLane] = React.useState<string | null>(null)\n const handleActionClick = React.useCallback((event: React.MouseEvent) => {\n event.stopPropagation()\n }, [])\n\n const handleSortChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {\n const value = event.target.value as SortOption\n if (sortOptions.includes(value)) setSortBy(value)\n }, [])\n\n const handleDragStart = React.useCallback((dealId: string) => {\n setDraggingId(dealId)\n }, [])\n\n const handleDragEnd = React.useCallback(() => {\n setDraggingId(null)\n setActiveLane(null)\n }, [])\n\n const handleDrop = React.useCallback(\n (stage: StageDefinition) => async (event: React.DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n setActiveLane(null)\n const dealId = event.dataTransfer.getData('text/plain') || draggingId\n if (!dealId) return\n const deal = dealMap.get(dealId)\n if (!deal) return\n if (stage.value === null) {\n flash(\n translate('customers.deals.pipeline.unassignedDisabled', 'Moving to \"No stage\" is not supported.'),\n 'info',\n )\n return\n }\n if (deal.pipelineStageId === stage.value) return\n updateStageMutation.mutate({ id: dealId, pipelineStageId: stage.value })\n },\n [dealMap, draggingId, translate, updateStageMutation],\n )\n\n const handleDragOver = React.useCallback(\n (stageId: string) => (event: React.DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n event.dataTransfer.dropEffect = 'move'\n if (activeLane !== stageId) setActiveLane(stageId)\n },\n [activeLane],\n )\n\n const renderLaneHeader = (stage: StageDefinition, count: number) => {\n return (\n <div className=\"flex items-center justify-between gap-3 border-b border-border px-4 py-3\">\n <div className=\"flex items-center gap-2\">\n <div className=\"flex flex-col\">\n <span className=\"text-sm font-medium\">{stage.label}</span>\n <span className=\"text-xs text-muted-foreground\">\n {translate('customers.deals.pipeline.countLabel', 'Deals: {count}', { count })}\n </span>\n </div>\n </div>\n </div>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex flex-col\">\n <h1 className=\"text-xl font-semibold text-foreground\">\n {translate('customers.deals.pipeline.title', 'Sales Pipeline')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {translate(\n 'customers.deals.pipeline.subtitle',\n 'Track deals by pipeline stage and drag them between lanes to update progress.',\n )}\n </p>\n </div>\n <div className=\"flex items-center gap-4\">\n {pipelinesQuery.data && pipelinesQuery.data.length > 0 ? (\n <label className=\"flex items-center gap-2 text-sm font-medium text-muted-foreground\">\n <span>{translate('customers.deals.pipeline.switch.label', 'Pipeline')}</span>\n <select\n className=\"h-9 rounded-md border border-border bg-background px-3 text-sm text-foreground shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40\"\n value={selectedPipelineId ?? ''}\n onChange={(e) => setSelectedPipelineId(e.target.value || null)}\n >\n {pipelinesQuery.data.map((p) => (\n <option key={p.id} value={p.id}>{p.name}</option>\n ))}\n </select>\n </label>\n ) : null}\n <Link\n href=\"/backend/config/customers/pipeline-stages\"\n className=\"text-sm font-medium text-primary hover:underline\"\n >\n {translate('customers.deals.pipeline.manageStages', 'Manage stages')}\n </Link>\n <label className=\"flex items-center gap-2 text-sm font-medium text-muted-foreground\">\n <span>{translate('customers.deals.pipeline.sort.label', 'Sort by')}</span>\n <select\n className=\"h-9 rounded-md border border-border bg-background px-3 text-sm text-foreground shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40\"\n value={sortBy}\n onChange={handleSortChange}\n >\n <option value=\"probability\">\n {translate('customers.deals.pipeline.sort.probability', 'Probability (high to low)')}\n </option>\n <option value=\"createdAt\">\n {translate('customers.deals.pipeline.sort.createdAt', 'Created (newest first)')}\n </option>\n <option value=\"expectedCloseAt\">\n {translate('customers.deals.pipeline.sort.expectedCloseAt', 'Expected close (soonest first)')}\n </option>\n </select>\n </label>\n </div>\n </div>\n\n {!selectedPipelineId ? (\n <div className=\"flex h-[50vh] items-center justify-center\">\n <span className=\"text-sm text-muted-foreground\">\n {translate('customers.deals.pipeline.noPipeline', 'No pipeline selected. Create a pipeline in settings.')}\n </span>\n </div>\n ) : dealsQuery.isLoading ? (\n <div className=\"flex h-[50vh] items-center justify-center\">\n <Spinner />\n </div>\n ) : dealsQuery.isError ? (\n <div className=\"max-w-xl\">\n <ErrorNotice\n message={\n dealsQuery.error instanceof Error\n ? dealsQuery.error.message\n : translate('customers.deals.pipeline.loadError', 'Failed to load deals.')\n }\n />\n </div>\n ) : (\n <div className=\"flex flex-col gap-3\">\n {total > deals.length ? (\n <div className=\"rounded-md border border-border bg-muted/30 px-4 py-2 text-sm text-muted-foreground\">\n {translate(\n 'customers.deals.pipeline.limitNotice',\n 'Showing the first {count} deals. Refine your filters to see more.',\n { count: deals.length },\n )}\n </div>\n ) : null}\n\n <div className=\"flex flex-col gap-4 pb-6 md:flex-row md:overflow-x-auto\">\n {stages.length === 0 ? (\n <div className=\"flex h-[50vh] w-full items-center justify-center rounded-lg border border-dashed border-border bg-muted/30\">\n <span className=\"text-sm text-muted-foreground\">\n {translate('customers.deals.pipeline.noStages', 'Define pipeline stages to start tracking deals.')}\n </span>\n </div>\n ) : (\n stages.map((stage) => {\n const stageKey = stage.value ?? null\n const laneDeals = groupedDeals.get(stageKey) ?? []\n const sortedLaneDeals = sortDeals(laneDeals, sortBy)\n const isActive = activeLane === stage.id\n return (\n <div\n key={stage.id}\n className={`flex min-h-[60vh] w-full flex-1 flex-col overflow-hidden rounded-lg border border-border bg-card shadow-sm transition-all md:w-72 md:flex-none ${\n isActive ? 'ring-2 ring-ring/40' : ''\n }`}\n onDragOver={handleDragOver(stage.id)}\n onDrop={handleDrop(stage)}\n >\n {renderLaneHeader(stage, laneDeals.length)}\n <div className=\"flex flex-1 flex-col gap-3 overflow-y-auto px-4 py-3\">\n {sortedLaneDeals.length === 0 ? (\n <div className=\"rounded-md border border-dashed border-border bg-muted/30 p-4 text-center text-xs text-muted-foreground\">\n {translate('customers.deals.pipeline.emptyLane', 'No deals in this stage yet.')}\n </div>\n ) : (\n sortedLaneDeals.map((deal) => {\n const isDragging = draggingId === deal.id\n || (pendingDealId === deal.id && updateStageMutation.isPending)\n const valueLabel = formatCurrency(\n deal.valueAmount,\n deal.valueCurrency,\n translate('customers.deals.list.noValue', 'No value assigned'),\n )\n const probabilityLabel = formatProbability(\n deal.probability,\n translate('customers.deals.pipeline.noProbability', 'N/A'),\n )\n const expectedLabel = deal.expectedCloseAt\n ? dateFormatter.format(new Date(deal.expectedCloseAt))\n : translate('customers.deals.pipeline.noExpectedClose', 'No date')\n return (\n <div\n key={deal.id}\n className={`group flex cursor-grab flex-col gap-2 rounded-md border border-border bg-background p-4 shadow-xs transition ${\n isDragging ? 'opacity-50' : 'hover:shadow-sm'\n }`}\n draggable\n onDragStart={(event) => {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', deal.id)\n handleDragStart(deal.id)\n }}\n onDragEnd={handleDragEnd}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex flex-col\">\n <span className=\"line-clamp-2 text-sm font-medium text-foreground\">\n {deal.title}\n </span>\n {deal.status ? (\n <span className=\"text-xs uppercase tracking-wide text-muted-foreground\">\n {deal.status}\n </span>\n ) : null}\n </div>\n {pendingDealId === deal.id && updateStageMutation.isPending ? (\n <Spinner className=\"size-4\" />\n ) : null}\n </div>\n <div className=\"flex flex-col gap-1 text-xs text-muted-foreground\">\n <div className=\"flex items-center justify-between gap-2\">\n <span>{translate('customers.deals.pipeline.card.value', 'Value')}</span>\n <span className=\"font-medium text-foreground\">{valueLabel}</span>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <span>{translate('customers.deals.pipeline.card.probability', 'Probability')}</span>\n <span className=\"font-medium text-foreground\">{probabilityLabel}</span>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <span>{translate('customers.deals.pipeline.card.expectedClose', 'Expected close')}</span>\n <span className=\"font-medium text-foreground\">{expectedLabel}</span>\n </div>\n </div>\n <div className=\"mt-1 flex flex-wrap gap-2 text-xs\">\n <Link\n href={`/backend/customers/deals/${deal.id}`}\n className=\"font-medium text-primary hover:underline\"\n draggable={false}\n onClick={handleActionClick}\n >\n {translate('customers.deals.pipeline.actions.openDeal', 'Open deal')}\n </Link>\n </div>\n {deal.people.length ? (\n <div className=\"flex flex-wrap gap-2\">\n {deal.people.map((person) => (\n <Link\n key={person.id}\n className=\"rounded-full bg-primary/5 px-3 py-1 text-xs text-primary transition-colors hover:bg-primary/10\"\n href={`/backend/customers/people-v2/${person.id}`}\n draggable={false}\n onClick={handleActionClick}\n >\n {person.label}\n </Link>\n ))}\n </div>\n ) : null}\n {deal.companies.length ? (\n <div className=\"flex flex-wrap gap-2\">\n {deal.companies.map((company) => (\n <Link\n key={company.id}\n className=\"rounded-full bg-secondary/10 px-3 py-1 text-xs text-secondary-foreground transition-colors hover:bg-secondary/20\"\n href={`/backend/customers/companies-v2/${company.id}`}\n draggable={false}\n onClick={handleActionClick}\n >\n {company.label}\n </Link>\n ))}\n </div>\n ) : null}\n </div>\n )\n })\n )}\n </div>\n </div>\n )\n })\n )}\n </div>\n </div>\n )}\n </div>\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { ErrorNotice } from '@open-mercato/ui/primitives/ErrorNotice'\nimport { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\n\ntype DealAssociation = { id: string; label: string }\n\ntype DealRecord = {\n id: string\n title: string\n status: string | null\n pipelineStage: string | null\n pipelineId: string | null\n pipelineStageId: string | null\n valueAmount: number | null\n valueCurrency: string | null\n probability: number | null\n expectedCloseAt: string | null\n expectedCloseAtTs: number | null\n createdAt: string | null\n createdAtTs: number | null\n updatedAt: string | null\n people: DealAssociation[]\n companies: DealAssociation[]\n}\n\ntype DealsQueryData = {\n deals: DealRecord[]\n total: number\n}\n\ntype StageDefinition = {\n id: string\n value: string | null\n label: string\n color: string | null\n icon: string | null\n}\n\ntype SortOption = 'probability' | 'createdAt' | 'expectedCloseAt'\n\ntype PipelineRecord = { id: string; name: string; isDefault: boolean }\ntype PipelineStageRecord = { id: string; label: string; order: number; pipelineId: string }\n\nconst DEALS_QUERY_LIMIT = 100\n\nconst dealsQueryKey = (scopeVersion: number, pipelineId: string | null) =>\n ['customers', 'deals', 'pipeline', `scope:${scopeVersion}`, `pipeline:${pipelineId ?? 'none'}`] as const\n\nconst sortOptions: SortOption[] = ['probability', 'createdAt', 'expectedCloseAt']\n\nfunction normalizeAmount(value: unknown): number | null {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string') {\n const trimmed = value.trim()\n if (!trimmed.length) return null\n const parsed = Number(trimmed)\n return Number.isFinite(parsed) ? parsed : null\n }\n return null\n}\n\nfunction normalizeProbability(value: unknown): number | null {\n const parsed = normalizeAmount(value)\n if (parsed === null) return null\n if (parsed < 0) return 0\n if (parsed > 100) return 100\n return Math.round(parsed)\n}\n\nfunction normalizeTimestamp(value: unknown): { iso: string | null; ts: number | null } {\n if (typeof value !== 'string' || !value.trim().length) return { iso: null, ts: null }\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return { iso: null, ts: null }\n return { iso: date.toISOString(), ts: date.getTime() }\n}\n\nfunction buildStageDefinitionsFromPipelineStages(\n pipelineStages: PipelineStageRecord[],\n deals: DealRecord[],\n t: ReturnType<typeof useT>,\n): StageDefinition[] {\n const result: StageDefinition[] = pipelineStages\n .slice()\n .sort((a, b) => a.order - b.order)\n .map((stage) => ({\n id: stage.id,\n value: stage.id,\n label: stage.label,\n color: null,\n icon: null,\n }))\n\n const knownIds = new Set(pipelineStages.map((s) => s.id))\n const hasUnassigned = deals.some((deal) => !deal.pipelineStageId || !knownIds.has(deal.pipelineStageId))\n if (hasUnassigned) {\n result.push({\n id: 'stage:__unassigned',\n value: null,\n label: translateWithFallback(t, 'customers.deals.pipeline.unassigned', 'No stage'),\n color: null,\n icon: null,\n })\n }\n\n return result\n}\n\nfunction createDealMap(deals: DealRecord[]): Map<string, DealRecord> {\n return deals.reduce<Map<string, DealRecord>>((acc, deal) => acc.set(deal.id, deal), new Map())\n}\n\nfunction groupDealsByStageId(deals: DealRecord[]): Map<string | null, DealRecord[]> {\n const byStage = new Map<string | null, DealRecord[]>()\n deals.forEach((deal) => {\n const stageKey = deal.pipelineStageId ?? null\n const bucket = byStage.get(stageKey) ?? []\n bucket.push(deal)\n byStage.set(stageKey, bucket)\n })\n return byStage\n}\n\nfunction sortDeals(deals: DealRecord[], option: SortOption): DealRecord[] {\n const sorted = [...deals]\n sorted.sort((a, b) => {\n if (option === 'probability') {\n const ap = typeof a.probability === 'number' ? a.probability : -1\n const bp = typeof b.probability === 'number' ? b.probability : -1\n if (ap !== bp) return bp - ap\n }\n if (option === 'expectedCloseAt') {\n const at = typeof a.expectedCloseAtTs === 'number' ? a.expectedCloseAtTs : Number.POSITIVE_INFINITY\n const bt = typeof b.expectedCloseAtTs === 'number' ? b.expectedCloseAtTs : Number.POSITIVE_INFINITY\n if (at !== bt) return at - bt\n }\n const at = typeof a.createdAtTs === 'number' ? a.createdAtTs : Number.NEGATIVE_INFINITY\n const bt = typeof b.createdAtTs === 'number' ? b.createdAtTs : Number.NEGATIVE_INFINITY\n if (option === 'createdAt') {\n if (at !== bt) return bt - at\n } else if (option === 'expectedCloseAt' || option === 'probability') {\n if (at !== bt) return bt - at\n }\n return a.title.localeCompare(b.title, undefined, { sensitivity: 'base' })\n })\n return sorted\n}\n\nfunction formatCurrency(amount: number | null, currency: string | null, fallback: string): string {\n if (amount === null || Number.isNaN(amount)) return fallback\n const code = currency && currency.length === 3 ? currency.toUpperCase() : 'USD'\n try {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency: code,\n maximumFractionDigits: 2,\n }).format(amount)\n } catch {\n return `${code} ${amount.toFixed(2)}`\n }\n}\n\nfunction formatProbability(probability: number | null, fallback: string): string {\n if (typeof probability !== 'number' || Number.isNaN(probability)) return fallback\n return `${probability}%`\n}\n\nexport default function SalesPipelinePage(): React.ReactElement {\n const t = useT()\n const translate = React.useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>) => {\n const value = translateWithFallback(t, key, fallback, params)\n if (value === fallback && params) {\n return fallback.replace(/\\{\\{(\\w+)\\}\\}|\\{(\\w+)\\}/g, (match, doubleToken, singleToken) => {\n const token = (doubleToken ?? singleToken) as string | undefined\n if (!token) return match\n const replacement = params[token]\n if (replacement === undefined) {\n return doubleToken ? `{{${token}}}` : `{${token}}`\n }\n return String(replacement)\n })\n }\n return value\n },\n [t],\n )\n const scopeVersion = useOrganizationScopeVersion()\n const queryClient = useQueryClient()\n const [sortBy, setSortBy] = React.useState<SortOption>('probability')\n const [pendingDealId, setPendingDealId] = React.useState<string | null>(null)\n const [selectedPipelineId, setSelectedPipelineId] = React.useState<string | null>(null)\n\n const pipelinesQuery = useQuery<PipelineRecord[]>({\n queryKey: ['customers', 'pipelines', `scope:${scopeVersion}`],\n staleTime: 60_000,\n queryFn: async () => {\n const payload = await readApiResultOrThrow<{ items: PipelineRecord[] }>(\n '/api/customers/pipelines',\n undefined,\n { errorMessage: translate('customers.deals.pipeline.loadError', 'Failed to load pipelines.') },\n )\n return payload?.items ?? []\n },\n })\n\n React.useEffect(() => {\n if (selectedPipelineId) return\n const pipelines = pipelinesQuery.data\n if (!pipelines || !pipelines.length) return\n const defaultPipeline = pipelines.find((p) => p.isDefault) ?? pipelines[0]\n if (defaultPipeline) setSelectedPipelineId(defaultPipeline.id)\n }, [pipelinesQuery.data, selectedPipelineId])\n\n const stagesQuery = useQuery<PipelineStageRecord[]>({\n queryKey: ['customers', 'pipeline-stages', `scope:${scopeVersion}`, `pipeline:${selectedPipelineId}`],\n enabled: !!selectedPipelineId,\n staleTime: 30_000,\n queryFn: async () => {\n const payload = await readApiResultOrThrow<{ items: PipelineStageRecord[] }>(\n `/api/customers/pipeline-stages?pipelineId=${encodeURIComponent(selectedPipelineId!)}`,\n undefined,\n { errorMessage: translate('customers.deals.pipeline.loadError', 'Failed to load stages.') },\n )\n return payload?.items ?? []\n },\n })\n\n const dealsKey = React.useMemo(() => dealsQueryKey(scopeVersion, selectedPipelineId), [scopeVersion, selectedPipelineId])\n\n const dealsQuery = useQuery<DealsQueryData>({\n queryKey: dealsKey,\n enabled: !!selectedPipelineId,\n staleTime: 30_000,\n queryFn: async () => {\n const search = new URLSearchParams()\n search.set('page', '1')\n search.set('pageSize', String(DEALS_QUERY_LIMIT))\n search.set('sortField', 'createdAt')\n search.set('sortDir', 'desc')\n if (selectedPipelineId) search.set('pipelineId', selectedPipelineId)\n const payload = await readApiResultOrThrow<Record<string, unknown>>(\n `/api/customers/deals?${search.toString()}`,\n undefined,\n { errorMessage: translate('customers.deals.pipeline.loadError', 'Failed to load deals.') },\n )\n const items = Array.isArray(payload?.items) ? payload.items : []\n const deals: DealRecord[] = []\n items.forEach((item) => {\n if (!item || typeof item !== 'object') return\n const data = item as Record<string, unknown>\n const id = typeof data.id === 'string' ? data.id : null\n if (!id) return\n const title =\n typeof data.title === 'string' && data.title.trim().length\n ? data.title.trim()\n : translate('customers.deals.pipeline.untitled', 'Untitled deal')\n const status =\n typeof data.status === 'string' && data.status.trim().length ? data.status.trim() : null\n const stage =\n typeof data.pipeline_stage === 'string' && data.pipeline_stage.trim().length\n ? data.pipeline_stage.trim()\n : null\n const amount = normalizeAmount(data.value_amount)\n const currency =\n typeof data.value_currency === 'string' && data.value_currency.trim().length\n ? data.value_currency.trim().toUpperCase()\n : null\n const probability = normalizeProbability(data.probability)\n const expected = normalizeTimestamp(data.expected_close_at)\n const created = normalizeTimestamp(data.created_at)\n const updated = normalizeTimestamp(data.updated_at)\n const rawPeople = Array.isArray(data.people) ? data.people : []\n const people: DealAssociation[] = rawPeople\n .map((entry) => {\n if (!entry || typeof entry !== 'object') return null\n const ref = entry as Record<string, unknown>\n const personId = typeof ref.id === 'string' ? ref.id : null\n if (!personId) return null\n const label =\n typeof ref.label === 'string' && ref.label.trim().length\n ? ref.label.trim()\n : personId\n return { id: personId, label }\n })\n .filter((entry): entry is DealAssociation => !!entry)\n const rawCompanies = Array.isArray(data.companies) ? data.companies : []\n const companies: DealAssociation[] = rawCompanies\n .map((entry) => {\n if (!entry || typeof entry !== 'object') return null\n const ref = entry as Record<string, unknown>\n const companyId = typeof ref.id === 'string' ? ref.id : null\n if (!companyId) return null\n const label =\n typeof ref.label === 'string' && ref.label.trim().length\n ? ref.label.trim()\n : companyId\n return { id: companyId, label }\n })\n .filter((entry): entry is DealAssociation => !!entry)\n deals.push({\n id,\n title,\n status,\n pipelineStage: stage,\n pipelineId: typeof data.pipeline_id === 'string' ? data.pipeline_id : null,\n pipelineStageId: typeof data.pipeline_stage_id === 'string' ? data.pipeline_stage_id : null,\n valueAmount: amount,\n valueCurrency: currency,\n probability,\n expectedCloseAt: expected.iso,\n expectedCloseAtTs: expected.ts,\n createdAt: created.iso,\n createdAtTs: created.ts,\n updatedAt: updated.iso,\n people,\n companies,\n })\n })\n\n const total = typeof payload?.total === 'number' ? payload.total : deals.length\n return { deals, total }\n },\n })\n\n const deals = dealsQuery.data?.deals ?? []\n const total = dealsQuery.data?.total ?? deals.length\n const dealMap = React.useMemo(() => createDealMap(deals), [deals])\n const groupedDeals = React.useMemo(() => groupDealsByStageId(deals), [deals])\n const stages = React.useMemo(\n () => buildStageDefinitionsFromPipelineStages(stagesQuery.data ?? [], deals, t),\n [stagesQuery.data, deals, t],\n )\n\n const dateFormatter = React.useMemo(\n () =>\n new Intl.DateTimeFormat(undefined, {\n dateStyle: 'medium',\n }),\n [],\n )\n\n const updateStageMutation = useMutation({\n mutationFn: async ({ id, pipelineStageId }: { id: string; pipelineStageId: string }) => {\n await apiCallOrThrow(\n '/api/customers/deals',\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id, pipelineStageId }),\n },\n { errorMessage: translate('customers.deals.pipeline.moveError', 'Failed to update deal stage.') },\n )\n return { id, pipelineStageId }\n },\n onMutate: async ({ id, pipelineStageId }) => {\n setPendingDealId(id)\n await queryClient.cancelQueries({ queryKey: dealsKey })\n const previous = queryClient.getQueryData<DealsQueryData>(dealsKey)\n if (previous) {\n const nextDeals = previous.deals.map((deal) =>\n deal.id === id ? { ...deal, pipelineStageId } : deal,\n )\n queryClient.setQueryData<DealsQueryData>(dealsKey, { ...previous, deals: nextDeals })\n }\n return { previous }\n },\n onError: (error, _variables, context) => {\n if (context?.previous) {\n queryClient.setQueryData<DealsQueryData>(dealsKey, context.previous)\n }\n const message =\n error instanceof Error && error.message\n ? error.message\n : translate('customers.deals.pipeline.moveError', 'Failed to update deal stage.')\n flash(message, 'error')\n },\n onSuccess: () => {\n flash(translate('customers.deals.pipeline.moveSuccess', 'Deal updated.'), 'success')\n },\n onSettled: () => {\n setPendingDealId(null)\n queryClient.invalidateQueries({ queryKey: dealsKey }).catch(() => {})\n },\n })\n\n const [draggingId, setDraggingId] = React.useState<string | null>(null)\n const [activeLane, setActiveLane] = React.useState<string | null>(null)\n const handleActionClick = React.useCallback((event: React.MouseEvent) => {\n event.stopPropagation()\n }, [])\n\n const handleSortChange = React.useCallback((value: string) => {\n if (sortOptions.includes(value as SortOption)) setSortBy(value as SortOption)\n }, [])\n\n const handleDragStart = React.useCallback((dealId: string) => {\n setDraggingId(dealId)\n }, [])\n\n const handleDragEnd = React.useCallback(() => {\n setDraggingId(null)\n setActiveLane(null)\n }, [])\n\n const handleDrop = React.useCallback(\n (stage: StageDefinition) => async (event: React.DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n setActiveLane(null)\n const dealId = event.dataTransfer.getData('text/plain') || draggingId\n if (!dealId) return\n const deal = dealMap.get(dealId)\n if (!deal) return\n if (stage.value === null) {\n flash(\n translate('customers.deals.pipeline.unassignedDisabled', 'Moving to \"No stage\" is not supported.'),\n 'info',\n )\n return\n }\n if (deal.pipelineStageId === stage.value) return\n updateStageMutation.mutate({ id: dealId, pipelineStageId: stage.value })\n },\n [dealMap, draggingId, translate, updateStageMutation],\n )\n\n const handleDragOver = React.useCallback(\n (stageId: string) => (event: React.DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n event.dataTransfer.dropEffect = 'move'\n if (activeLane !== stageId) setActiveLane(stageId)\n },\n [activeLane],\n )\n\n const renderLaneHeader = (stage: StageDefinition, count: number) => {\n return (\n <div className=\"flex items-center justify-between gap-3 border-b border-border px-4 py-3\">\n <div className=\"flex items-center gap-2\">\n <div className=\"flex flex-col\">\n <span className=\"text-sm font-medium\">{stage.label}</span>\n <span className=\"text-xs text-muted-foreground\">\n {translate('customers.deals.pipeline.countLabel', 'Deals: {count}', { count })}\n </span>\n </div>\n </div>\n </div>\n )\n }\n\n return (\n <Page>\n <PageBody>\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex flex-col\">\n <h1 className=\"text-xl font-semibold text-foreground\">\n {translate('customers.deals.pipeline.title', 'Sales Pipeline')}\n </h1>\n <p className=\"text-sm text-muted-foreground\">\n {translate(\n 'customers.deals.pipeline.subtitle',\n 'Track deals by pipeline stage and drag them between lanes to update progress.',\n )}\n </p>\n </div>\n <div className=\"flex items-center gap-4\">\n {pipelinesQuery.data && pipelinesQuery.data.length > 0 ? (\n <label className=\"flex items-center gap-2 text-sm font-medium text-muted-foreground\">\n <span>{translate('customers.deals.pipeline.switch.label', 'Pipeline')}</span>\n <Select\n value={selectedPipelineId || undefined}\n onValueChange={(value) => setSelectedPipelineId(value || null)}\n >\n <SelectTrigger className=\"w-auto min-w-[12rem]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {pipelinesQuery.data.map((p) => (\n <SelectItem key={p.id} value={p.id}>{p.name}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n ) : null}\n <Link\n href=\"/backend/config/customers/pipeline-stages\"\n className=\"text-sm font-medium text-primary hover:underline\"\n >\n {translate('customers.deals.pipeline.manageStages', 'Manage stages')}\n </Link>\n <label className=\"flex items-center gap-2 text-sm font-medium text-muted-foreground\">\n <span>{translate('customers.deals.pipeline.sort.label', 'Sort by')}</span>\n <Select value={sortBy} onValueChange={handleSortChange}>\n <SelectTrigger className=\"w-auto min-w-[14rem]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"probability\">\n {translate('customers.deals.pipeline.sort.probability', 'Probability (high to low)')}\n </SelectItem>\n <SelectItem value=\"createdAt\">\n {translate('customers.deals.pipeline.sort.createdAt', 'Created (newest first)')}\n </SelectItem>\n <SelectItem value=\"expectedCloseAt\">\n {translate('customers.deals.pipeline.sort.expectedCloseAt', 'Expected close (soonest first)')}\n </SelectItem>\n </SelectContent>\n </Select>\n </label>\n </div>\n </div>\n\n {!selectedPipelineId ? (\n <div className=\"flex h-[50vh] items-center justify-center\">\n <span className=\"text-sm text-muted-foreground\">\n {translate('customers.deals.pipeline.noPipeline', 'No pipeline selected. Create a pipeline in settings.')}\n </span>\n </div>\n ) : dealsQuery.isLoading ? (\n <div className=\"flex h-[50vh] items-center justify-center\">\n <Spinner />\n </div>\n ) : dealsQuery.isError ? (\n <div className=\"max-w-xl\">\n <ErrorNotice\n message={\n dealsQuery.error instanceof Error\n ? dealsQuery.error.message\n : translate('customers.deals.pipeline.loadError', 'Failed to load deals.')\n }\n />\n </div>\n ) : (\n <div className=\"flex flex-col gap-3\">\n {total > deals.length ? (\n <div className=\"rounded-md border border-border bg-muted/30 px-4 py-2 text-sm text-muted-foreground\">\n {translate(\n 'customers.deals.pipeline.limitNotice',\n 'Showing the first {count} deals. Refine your filters to see more.',\n { count: deals.length },\n )}\n </div>\n ) : null}\n\n <div className=\"flex flex-col gap-4 pb-6 md:flex-row md:overflow-x-auto\">\n {stages.length === 0 ? (\n <div className=\"flex h-[50vh] w-full items-center justify-center rounded-lg border border-dashed border-border bg-muted/30\">\n <span className=\"text-sm text-muted-foreground\">\n {translate('customers.deals.pipeline.noStages', 'Define pipeline stages to start tracking deals.')}\n </span>\n </div>\n ) : (\n stages.map((stage) => {\n const stageKey = stage.value ?? null\n const laneDeals = groupedDeals.get(stageKey) ?? []\n const sortedLaneDeals = sortDeals(laneDeals, sortBy)\n const isActive = activeLane === stage.id\n return (\n <div\n key={stage.id}\n className={`flex min-h-[60vh] w-full flex-1 flex-col overflow-hidden rounded-lg border border-border bg-card shadow-sm transition-all md:w-72 md:flex-none ${\n isActive ? 'ring-2 ring-ring/40' : ''\n }`}\n onDragOver={handleDragOver(stage.id)}\n onDrop={handleDrop(stage)}\n >\n {renderLaneHeader(stage, laneDeals.length)}\n <div className=\"flex flex-1 flex-col gap-3 overflow-y-auto px-4 py-3\">\n {sortedLaneDeals.length === 0 ? (\n <div className=\"rounded-md border border-dashed border-border bg-muted/30 p-4 text-center text-xs text-muted-foreground\">\n {translate('customers.deals.pipeline.emptyLane', 'No deals in this stage yet.')}\n </div>\n ) : (\n sortedLaneDeals.map((deal) => {\n const isDragging = draggingId === deal.id\n || (pendingDealId === deal.id && updateStageMutation.isPending)\n const valueLabel = formatCurrency(\n deal.valueAmount,\n deal.valueCurrency,\n translate('customers.deals.list.noValue', 'No value assigned'),\n )\n const probabilityLabel = formatProbability(\n deal.probability,\n translate('customers.deals.pipeline.noProbability', 'N/A'),\n )\n const expectedLabel = deal.expectedCloseAt\n ? dateFormatter.format(new Date(deal.expectedCloseAt))\n : translate('customers.deals.pipeline.noExpectedClose', 'No date')\n return (\n <div\n key={deal.id}\n className={`group flex cursor-grab flex-col gap-2 rounded-md border border-border bg-background p-4 shadow-xs transition ${\n isDragging ? 'opacity-50' : 'hover:shadow-sm'\n }`}\n draggable\n onDragStart={(event) => {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', deal.id)\n handleDragStart(deal.id)\n }}\n onDragEnd={handleDragEnd}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex flex-col\">\n <span className=\"line-clamp-2 text-sm font-medium text-foreground\">\n {deal.title}\n </span>\n {deal.status ? (\n <span className=\"text-xs uppercase tracking-wide text-muted-foreground\">\n {deal.status}\n </span>\n ) : null}\n </div>\n {pendingDealId === deal.id && updateStageMutation.isPending ? (\n <Spinner className=\"size-4\" />\n ) : null}\n </div>\n <div className=\"flex flex-col gap-1 text-xs text-muted-foreground\">\n <div className=\"flex items-center justify-between gap-2\">\n <span>{translate('customers.deals.pipeline.card.value', 'Value')}</span>\n <span className=\"font-medium text-foreground\">{valueLabel}</span>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <span>{translate('customers.deals.pipeline.card.probability', 'Probability')}</span>\n <span className=\"font-medium text-foreground\">{probabilityLabel}</span>\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <span>{translate('customers.deals.pipeline.card.expectedClose', 'Expected close')}</span>\n <span className=\"font-medium text-foreground\">{expectedLabel}</span>\n </div>\n </div>\n <div className=\"mt-1 flex flex-wrap gap-2 text-xs\">\n <Link\n href={`/backend/customers/deals/${deal.id}`}\n className=\"font-medium text-primary hover:underline\"\n draggable={false}\n onClick={handleActionClick}\n >\n {translate('customers.deals.pipeline.actions.openDeal', 'Open deal')}\n </Link>\n </div>\n {deal.people.length ? (\n <div className=\"flex flex-wrap gap-2\">\n {deal.people.map((person) => (\n <Link\n key={person.id}\n className=\"rounded-full bg-primary/5 px-3 py-1 text-xs text-primary transition-colors hover:bg-primary/10\"\n href={`/backend/customers/people-v2/${person.id}`}\n draggable={false}\n onClick={handleActionClick}\n >\n {person.label}\n </Link>\n ))}\n </div>\n ) : null}\n {deal.companies.length ? (\n <div className=\"flex flex-wrap gap-2\">\n {deal.companies.map((company) => (\n <Link\n key={company.id}\n className=\"rounded-full bg-secondary/10 px-3 py-1 text-xs text-secondary-foreground transition-colors hover:bg-secondary/20\"\n href={`/backend/customers/companies-v2/${company.id}`}\n draggable={false}\n onClick={handleActionClick}\n >\n {company.label}\n </Link>\n ))}\n </div>\n ) : null}\n </div>\n )\n })\n )}\n </div>\n </div>\n )\n })\n )}\n </div>\n </div>\n )}\n </div>\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAucU,SACE,KADF;AArcV,YAAY,WAAW;AACvB,OAAO,UAAU;AACjB,SAAS,UAAU,aAAa,sBAAsB;AACtD,SAAS,MAAM,gBAAgB;AAC/B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,eAAe;AACxB,SAAS,mBAAmB;AAC5B,SAAS,gBAAgB,4BAA4B;AACrD,SAAS,aAAa;AACtB,SAAS,YAAY;AACrB,SAAS,6BAA6B;AACtC,SAAS,mCAAmC;AAyC5C,MAAM,oBAAoB;AAE1B,MAAM,gBAAgB,CAAC,cAAsB,eAC3C,CAAC,aAAa,SAAS,YAAY,SAAS,YAAY,IAAI,YAAY,cAAc,MAAM,EAAE;AAEhG,MAAM,cAA4B,CAAC,eAAe,aAAa,iBAAiB;AAEhF,SAAS,gBAAgB,OAA+B;AACtD,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO;AAChE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,CAAC,QAAQ,OAAQ,QAAO;AAC5B,UAAM,SAAS,OAAO,OAAO;AAC7B,WAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAAA,EAC5C;AACA,SAAO;AACT;AAEA,SAAS,qBAAqB,OAA+B;AAC3D,QAAM,SAAS,gBAAgB,KAAK;AACpC,MAAI,WAAW,KAAM,QAAO;AAC5B,MAAI,SAAS,EAAG,QAAO;AACvB,MAAI,SAAS,IAAK,QAAO;AACzB,SAAO,KAAK,MAAM,MAAM;AAC1B;AAEA,SAAS,mBAAmB,OAA2D;AACrF,MAAI,OAAO,UAAU,YAAY,CAAC,MAAM,KAAK,EAAE,OAAQ,QAAO,EAAE,KAAK,MAAM,IAAI,KAAK;AACpF,QAAM,OAAO,IAAI,KAAK,KAAK;AAC3B,MAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,EAAG,QAAO,EAAE,KAAK,MAAM,IAAI,KAAK;AAC/D,SAAO,EAAE,KAAK,KAAK,YAAY,GAAG,IAAI,KAAK,QAAQ,EAAE;AACvD;AAEA,SAAS,wCACP,gBACA,OACA,GACmB;AACnB,QAAM,SAA4B,eAC/B,MAAM,EACN,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAChC,IAAI,CAAC,WAAW;AAAA,IACf,IAAI,MAAM;AAAA,IACV,OAAO,MAAM;AAAA,IACb,OAAO,MAAM;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,EACR,EAAE;AAEJ,QAAM,WAAW,IAAI,IAAI,eAAe,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AACxD,QAAM,gBAAgB,MAAM,KAAK,CAAC,SAAS,CAAC,KAAK,mBAAmB,CAAC,SAAS,IAAI,KAAK,eAAe,CAAC;AACvG,MAAI,eAAe;AACjB,WAAO,KAAK;AAAA,MACV,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,OAAO,sBAAsB,GAAG,uCAAuC,UAAU;AAAA,MACjF,OAAO;AAAA,MACP,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAEA,SAAS,cAAc,OAA8C;AACnE,SAAO,MAAM,OAAgC,CAAC,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,IAAI,GAAG,oBAAI,IAAI,CAAC;AAC/F;AAEA,SAAS,oBAAoB,OAAuD;AAClF,QAAM,UAAU,oBAAI,IAAiC;AACrD,QAAM,QAAQ,CAAC,SAAS;AACtB,UAAM,WAAW,KAAK,mBAAmB;AACzC,UAAM,SAAS,QAAQ,IAAI,QAAQ,KAAK,CAAC;AACzC,WAAO,KAAK,IAAI;AAChB,YAAQ,IAAI,UAAU,MAAM;AAAA,EAC9B,CAAC;AACD,SAAO;AACT;AAEA,SAAS,UAAU,OAAqB,QAAkC;AACxE,QAAM,SAAS,CAAC,GAAG,KAAK;AACxB,SAAO,KAAK,CAAC,GAAG,MAAM;AACpB,QAAI,WAAW,eAAe;AAC5B,YAAM,KAAK,OAAO,EAAE,gBAAgB,WAAW,EAAE,cAAc;AAC/D,YAAM,KAAK,OAAO,EAAE,gBAAgB,WAAW,EAAE,cAAc;AAC/D,UAAI,OAAO,GAAI,QAAO,KAAK;AAAA,IAC7B;AACA,QAAI,WAAW,mBAAmB;AAChC,YAAMA,MAAK,OAAO,EAAE,sBAAsB,WAAW,EAAE,oBAAoB,OAAO;AAClF,YAAMC,MAAK,OAAO,EAAE,sBAAsB,WAAW,EAAE,oBAAoB,OAAO;AAClF,UAAID,QAAOC,IAAI,QAAOD,MAAKC;AAAA,IAC7B;AACA,UAAM,KAAK,OAAO,EAAE,gBAAgB,WAAW,EAAE,cAAc,OAAO;AACtE,UAAM,KAAK,OAAO,EAAE,gBAAgB,WAAW,EAAE,cAAc,OAAO;AACtE,QAAI,WAAW,aAAa;AAC1B,UAAI,OAAO,GAAI,QAAO,KAAK;AAAA,IAC7B,WAAW,WAAW,qBAAqB,WAAW,eAAe;AACnE,UAAI,OAAO,GAAI,QAAO,KAAK;AAAA,IAC7B;AACA,WAAO,EAAE,MAAM,cAAc,EAAE,OAAO,QAAW,EAAE,aAAa,OAAO,CAAC;AAAA,EAC1E,CAAC;AACD,SAAO;AACT;AAEA,SAAS,eAAe,QAAuB,UAAyB,UAA0B;AAChG,MAAI,WAAW,QAAQ,OAAO,MAAM,MAAM,EAAG,QAAO;AACpD,QAAM,OAAO,YAAY,SAAS,WAAW,IAAI,SAAS,YAAY,IAAI;AAC1E,MAAI;AACF,WAAO,IAAI,KAAK,aAAa,QAAW;AAAA,MACtC,OAAO;AAAA,MACP,UAAU;AAAA,MACV,uBAAuB;AAAA,IACzB,CAAC,EAAE,OAAO,MAAM;AAAA,EAClB,QAAQ;AACN,WAAO,GAAG,IAAI,IAAI,OAAO,QAAQ,CAAC,CAAC;AAAA,EACrC;AACF;AAEA,SAAS,kBAAkB,aAA4B,UAA0B;AAC/E,MAAI,OAAO,gBAAgB,YAAY,OAAO,MAAM,WAAW,EAAG,QAAO;AACzE,SAAO,GAAG,WAAW;AACvB;AAEe,SAAR,oBAAyD;AAC9D,QAAM,IAAI,KAAK;AACf,QAAM,YAAY,MAAM;AAAA,IACtB,CAAC,KAAa,UAAkB,WAA6C;AAC3E,YAAM,QAAQ,sBAAsB,GAAG,KAAK,UAAU,MAAM;AAC5D,UAAI,UAAU,YAAY,QAAQ;AAChC,eAAO,SAAS,QAAQ,4BAA4B,CAAC,OAAO,aAAa,gBAAgB;AACvF,gBAAM,QAAS,eAAe;AAC9B,cAAI,CAAC,MAAO,QAAO;AACnB,gBAAM,cAAc,OAAO,KAAK;AAChC,cAAI,gBAAgB,QAAW;AAC7B,mBAAO,cAAc,KAAK,KAAK,OAAO,IAAI,KAAK;AAAA,UACjD;AACA,iBAAO,OAAO,WAAW;AAAA,QAC3B,CAAC;AAAA,MACH;AACA,aAAO;AAAA,IACT;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AACA,QAAM,eAAe,4BAA4B;AACjD,QAAM,cAAc,eAAe;AACnC,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAqB,aAAa;AACpE,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAwB,IAAI;AAC5E,QAAM,CAAC,oBAAoB,qBAAqB,IAAI,MAAM,SAAwB,IAAI;AAEtF,QAAM,iBAAiB,SAA2B;AAAA,IAChD,UAAU,CAAC,aAAa,aAAa,SAAS,YAAY,EAAE;AAAA,IAC5D,WAAW;AAAA,IACX,SAAS,YAAY;AACnB,YAAM,UAAU,MAAM;AAAA,QACpB;AAAA,QACA;AAAA,QACA,EAAE,cAAc,UAAU,sCAAsC,2BAA2B,EAAE;AAAA,MAC/F;AACA,aAAO,SAAS,SAAS,CAAC;AAAA,IAC5B;AAAA,EACF,CAAC;AAED,QAAM,UAAU,MAAM;AACpB,QAAI,mBAAoB;AACxB,UAAM,YAAY,eAAe;AACjC,QAAI,CAAC,aAAa,CAAC,UAAU,OAAQ;AACrC,UAAM,kBAAkB,UAAU,KAAK,CAAC,MAAM,EAAE,SAAS,KAAK,UAAU,CAAC;AACzE,QAAI,gBAAiB,uBAAsB,gBAAgB,EAAE;AAAA,EAC/D,GAAG,CAAC,eAAe,MAAM,kBAAkB,CAAC;AAE5C,QAAM,cAAc,SAAgC;AAAA,IAClD,UAAU,CAAC,aAAa,mBAAmB,SAAS,YAAY,IAAI,YAAY,kBAAkB,EAAE;AAAA,IACpG,SAAS,CAAC,CAAC;AAAA,IACX,WAAW;AAAA,IACX,SAAS,YAAY;AACnB,YAAM,UAAU,MAAM;AAAA,QACpB,6CAA6C,mBAAmB,kBAAmB,CAAC;AAAA,QACpF;AAAA,QACA,EAAE,cAAc,UAAU,sCAAsC,wBAAwB,EAAE;AAAA,MAC5F;AACA,aAAO,SAAS,SAAS,CAAC;AAAA,IAC5B;AAAA,EACF,CAAC;AAED,QAAM,WAAW,MAAM,QAAQ,MAAM,cAAc,cAAc,kBAAkB,GAAG,CAAC,cAAc,kBAAkB,CAAC;AAExH,QAAM,aAAa,SAAyB;AAAA,IAC1C,UAAU;AAAA,IACV,SAAS,CAAC,CAAC;AAAA,IACX,WAAW;AAAA,IACX,SAAS,YAAY;AACnB,YAAM,SAAS,IAAI,gBAAgB;AACnC,aAAO,IAAI,QAAQ,GAAG;AACtB,aAAO,IAAI,YAAY,OAAO,iBAAiB,CAAC;AAChD,aAAO,IAAI,aAAa,WAAW;AACnC,aAAO,IAAI,WAAW,MAAM;AAC5B,UAAI,mBAAoB,QAAO,IAAI,cAAc,kBAAkB;AACnE,YAAM,UAAU,MAAM;AAAA,QACpB,wBAAwB,OAAO,SAAS,CAAC;AAAA,QACzC;AAAA,QACA,EAAE,cAAc,UAAU,sCAAsC,uBAAuB,EAAE;AAAA,MAC3F;AACA,YAAM,QAAQ,MAAM,QAAQ,SAAS,KAAK,IAAI,QAAQ,QAAQ,CAAC;AAC/D,YAAMC,SAAsB,CAAC;AAC7B,YAAM,QAAQ,CAAC,SAAS;AACtB,YAAI,CAAC,QAAQ,OAAO,SAAS,SAAU;AACvC,cAAM,OAAO;AACb,cAAM,KAAK,OAAO,KAAK,OAAO,WAAW,KAAK,KAAK;AACnD,YAAI,CAAC,GAAI;AACT,cAAM,QACJ,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,KAAK,EAAE,SAChD,KAAK,MAAM,KAAK,IAChB,UAAU,qCAAqC,eAAe;AACpE,cAAM,SACJ,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,KAAK,EAAE,SAAS,KAAK,OAAO,KAAK,IAAI;AACtF,cAAM,QACJ,OAAO,KAAK,mBAAmB,YAAY,KAAK,eAAe,KAAK,EAAE,SAClE,KAAK,eAAe,KAAK,IACzB;AACN,cAAM,SAAS,gBAAgB,KAAK,YAAY;AAChD,cAAM,WACJ,OAAO,KAAK,mBAAmB,YAAY,KAAK,eAAe,KAAK,EAAE,SAClE,KAAK,eAAe,KAAK,EAAE,YAAY,IACvC;AACN,cAAM,cAAc,qBAAqB,KAAK,WAAW;AACzD,cAAM,WAAW,mBAAmB,KAAK,iBAAiB;AAC1D,cAAM,UAAU,mBAAmB,KAAK,UAAU;AAClD,cAAM,UAAU,mBAAmB,KAAK,UAAU;AAClD,cAAM,YAAY,MAAM,QAAQ,KAAK,MAAM,IAAI,KAAK,SAAS,CAAC;AAC9D,cAAM,SAA4B,UAC/B,IAAI,CAAC,UAAU;AACd,cAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,gBAAM,MAAM;AACZ,gBAAM,WAAW,OAAO,IAAI,OAAO,WAAW,IAAI,KAAK;AACvD,cAAI,CAAC,SAAU,QAAO;AACtB,gBAAM,QACJ,OAAO,IAAI,UAAU,YAAY,IAAI,MAAM,KAAK,EAAE,SAC9C,IAAI,MAAM,KAAK,IACf;AACN,iBAAO,EAAE,IAAI,UAAU,MAAM;AAAA,QAC/B,CAAC,EACA,OAAO,CAAC,UAAoC,CAAC,CAAC,KAAK;AACtD,cAAM,eAAe,MAAM,QAAQ,KAAK,SAAS,IAAI,KAAK,YAAY,CAAC;AACvE,cAAM,YAA+B,aAClC,IAAI,CAAC,UAAU;AACd,cAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,gBAAM,MAAM;AACZ,gBAAM,YAAY,OAAO,IAAI,OAAO,WAAW,IAAI,KAAK;AACxD,cAAI,CAAC,UAAW,QAAO;AACvB,gBAAM,QACJ,OAAO,IAAI,UAAU,YAAY,IAAI,MAAM,KAAK,EAAE,SAC9C,IAAI,MAAM,KAAK,IACf;AACN,iBAAO,EAAE,IAAI,WAAW,MAAM;AAAA,QAChC,CAAC,EACA,OAAO,CAAC,UAAoC,CAAC,CAAC,KAAK;AACtD,QAAAA,OAAM,KAAK;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,UACA,eAAe;AAAA,UACf,YAAY,OAAO,KAAK,gBAAgB,WAAW,KAAK,cAAc;AAAA,UACtE,iBAAiB,OAAO,KAAK,sBAAsB,WAAW,KAAK,oBAAoB;AAAA,UACvF,aAAa;AAAA,UACb,eAAe;AAAA,UACf;AAAA,UACA,iBAAiB,SAAS;AAAA,UAC1B,mBAAmB,SAAS;AAAA,UAC5B,WAAW,QAAQ;AAAA,UACnB,aAAa,QAAQ;AAAA,UACrB,WAAW,QAAQ;AAAA,UACnB;AAAA,UACA;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAED,YAAMC,SAAQ,OAAO,SAAS,UAAU,WAAW,QAAQ,QAAQD,OAAM;AACzE,aAAO,EAAE,OAAAA,QAAO,OAAAC,OAAM;AAAA,IACxB;AAAA,EACF,CAAC;AAED,QAAM,QAAQ,WAAW,MAAM,SAAS,CAAC;AACzC,QAAM,QAAQ,WAAW,MAAM,SAAS,MAAM;AAC9C,QAAM,UAAU,MAAM,QAAQ,MAAM,cAAc,KAAK,GAAG,CAAC,KAAK,CAAC;AACjE,QAAM,eAAe,MAAM,QAAQ,MAAM,oBAAoB,KAAK,GAAG,CAAC,KAAK,CAAC;AAC5E,QAAM,SAAS,MAAM;AAAA,IACnB,MAAM,wCAAwC,YAAY,QAAQ,CAAC,GAAG,OAAO,CAAC;AAAA,IAC9E,CAAC,YAAY,MAAM,OAAO,CAAC;AAAA,EAC7B;AAEA,QAAM,gBAAgB,MAAM;AAAA,IAC1B,MACE,IAAI,KAAK,eAAe,QAAW;AAAA,MACjC,WAAW;AAAA,IACb,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAEA,QAAM,sBAAsB,YAAY;AAAA,IACtC,YAAY,OAAO,EAAE,IAAI,gBAAgB,MAA+C;AACtF,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,IAAI,gBAAgB,CAAC;AAAA,QAC9C;AAAA,QACA,EAAE,cAAc,UAAU,sCAAsC,8BAA8B,EAAE;AAAA,MAClG;AACA,aAAO,EAAE,IAAI,gBAAgB;AAAA,IAC/B;AAAA,IACA,UAAU,OAAO,EAAE,IAAI,gBAAgB,MAAM;AAC3C,uBAAiB,EAAE;AACnB,YAAM,YAAY,cAAc,EAAE,UAAU,SAAS,CAAC;AACtD,YAAM,WAAW,YAAY,aAA6B,QAAQ;AAClE,UAAI,UAAU;AACZ,cAAM,YAAY,SAAS,MAAM;AAAA,UAAI,CAAC,SACpC,KAAK,OAAO,KAAK,EAAE,GAAG,MAAM,gBAAgB,IAAI;AAAA,QAClD;AACA,oBAAY,aAA6B,UAAU,EAAE,GAAG,UAAU,OAAO,UAAU,CAAC;AAAA,MACtF;AACA,aAAO,EAAE,SAAS;AAAA,IACpB;AAAA,IACA,SAAS,CAAC,OAAO,YAAY,YAAY;AACvC,UAAI,SAAS,UAAU;AACrB,oBAAY,aAA6B,UAAU,QAAQ,QAAQ;AAAA,MACrE;AACA,YAAM,UACJ,iBAAiB,SAAS,MAAM,UAC5B,MAAM,UACN,UAAU,sCAAsC,8BAA8B;AACpF,YAAM,SAAS,OAAO;AAAA,IACxB;AAAA,IACA,WAAW,MAAM;AACf,YAAM,UAAU,wCAAwC,eAAe,GAAG,SAAS;AAAA,IACrF;AAAA,IACA,WAAW,MAAM;AACf,uBAAiB,IAAI;AACrB,kBAAY,kBAAkB,EAAE,UAAU,SAAS,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACtE;AAAA,EACF,CAAC;AAED,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAwB,IAAI;AACtE,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAwB,IAAI;AACtE,QAAM,oBAAoB,MAAM,YAAY,CAAC,UAA4B;AACvE,UAAM,gBAAgB;AAAA,EACxB,GAAG,CAAC,CAAC;AAEL,QAAM,mBAAmB,MAAM,YAAY,CAAC,UAAkB;AAC5D,QAAI,YAAY,SAAS,KAAmB,EAAG,WAAU,KAAmB;AAAA,EAC9E,GAAG,CAAC,CAAC;AAEL,QAAM,kBAAkB,MAAM,YAAY,CAAC,WAAmB;AAC5D,kBAAc,MAAM;AAAA,EACtB,GAAG,CAAC,CAAC;AAEL,QAAM,gBAAgB,MAAM,YAAY,MAAM;AAC5C,kBAAc,IAAI;AAClB,kBAAc,IAAI;AAAA,EACpB,GAAG,CAAC,CAAC;AAEL,QAAM,aAAa,MAAM;AAAA,IACvB,CAAC,UAA2B,OAAO,UAA2C;AAC5E,YAAM,eAAe;AACrB,oBAAc,IAAI;AAClB,YAAM,SAAS,MAAM,aAAa,QAAQ,YAAY,KAAK;AAC3D,UAAI,CAAC,OAAQ;AACb,YAAM,OAAO,QAAQ,IAAI,MAAM;AAC/B,UAAI,CAAC,KAAM;AACX,UAAI,MAAM,UAAU,MAAM;AACxB;AAAA,UACE,UAAU,+CAA+C,wCAAwC;AAAA,UACjG;AAAA,QACF;AACA;AAAA,MACF;AACA,UAAI,KAAK,oBAAoB,MAAM,MAAO;AAC1C,0BAAoB,OAAO,EAAE,IAAI,QAAQ,iBAAiB,MAAM,MAAM,CAAC;AAAA,IACzE;AAAA,IACA,CAAC,SAAS,YAAY,WAAW,mBAAmB;AAAA,EACtD;AAEA,QAAM,iBAAiB,MAAM;AAAA,IAC3B,CAAC,YAAoB,CAAC,UAA2C;AAC/D,YAAM,eAAe;AACrB,YAAM,aAAa,aAAa;AAChC,UAAI,eAAe,QAAS,eAAc,OAAO;AAAA,IACnD;AAAA,IACA,CAAC,UAAU;AAAA,EACb;AAEA,QAAM,mBAAmB,CAAC,OAAwB,UAAkB;AAClE,WACE,oBAAC,SAAI,WAAU,4EACb,8BAAC,SAAI,WAAU,2BACb,+BAAC,SAAI,WAAU,iBACb;AAAA,0BAAC,UAAK,WAAU,uBAAuB,gBAAM,OAAM;AAAA,MACnD,oBAAC,UAAK,WAAU,iCACb,oBAAU,uCAAuC,kBAAkB,EAAE,MAAM,CAAC,GAC/E;AAAA,OACF,GACF,GACF;AAAA,EAEJ;AAEA,SACE,oBAAC,QACC,8BAAC,YACC,+BAAC,SAAI,WAAU,uBACb;AAAA,yBAAC,SAAI,WAAU,sEACb;AAAA,2BAAC,SAAI,WAAU,iBACb;AAAA,4BAAC,QAAG,WAAU,yCACX,oBAAU,kCAAkC,gBAAgB,GAC/D;AAAA,QACA,oBAAC,OAAE,WAAU,iCACV;AAAA,UACC;AAAA,UACA;AAAA,QACF,GACF;AAAA,SACF;AAAA,MACA,qBAAC,SAAI,WAAU,2BACZ;AAAA,uBAAe,QAAQ,eAAe,KAAK,SAAS,IACnD,qBAAC,WAAM,WAAU,qEACf;AAAA,8BAAC,UAAM,oBAAU,yCAAyC,UAAU,GAAE;AAAA,UACtE;AAAA,YAAC;AAAA;AAAA,cACC,OAAO,sBAAsB;AAAA,cAC7B,eAAe,CAAC,UAAU,sBAAsB,SAAS,IAAI;AAAA,cAE7D;AAAA,oCAAC,iBAAc,WAAU,wBACvB,8BAAC,eAAY,GACf;AAAA,gBACA,oBAAC,iBACE,yBAAe,KAAK,IAAI,CAAC,MACxB,oBAAC,cAAsB,OAAO,EAAE,IAAK,YAAE,QAAtB,EAAE,EAAyB,CAC7C,GACH;AAAA;AAAA;AAAA,UACF;AAAA,WACF,IACE;AAAA,QACJ;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,WAAU;AAAA,YAET,oBAAU,yCAAyC,eAAe;AAAA;AAAA,QACrE;AAAA,QACA,qBAAC,WAAM,WAAU,qEACf;AAAA,8BAAC,UAAM,oBAAU,uCAAuC,SAAS,GAAE;AAAA,UACnE,qBAAC,UAAO,OAAO,QAAQ,eAAe,kBACpC;AAAA,gCAAC,iBAAc,WAAU,wBACvB,8BAAC,eAAY,GACf;AAAA,YACA,qBAAC,iBACC;AAAA,kCAAC,cAAW,OAAM,eACf,oBAAU,6CAA6C,2BAA2B,GACrF;AAAA,cACA,oBAAC,cAAW,OAAM,aACf,oBAAU,2CAA2C,wBAAwB,GAChF;AAAA,cACA,oBAAC,cAAW,OAAM,mBACf,oBAAU,iDAAiD,gCAAgC,GAC9F;AAAA,eACF;AAAA,aACF;AAAA,WACF;AAAA,SACF;AAAA,OACF;AAAA,IAEC,CAAC,qBACA,oBAAC,SAAI,WAAU,6CACb,8BAAC,UAAK,WAAU,iCACb,oBAAU,uCAAuC,sDAAsD,GAC1G,GACF,IACE,WAAW,YACb,oBAAC,SAAI,WAAU,6CACb,8BAAC,WAAQ,GACX,IACE,WAAW,UACb,oBAAC,SAAI,WAAU,YACb;AAAA,MAAC;AAAA;AAAA,QACC,SACE,WAAW,iBAAiB,QACxB,WAAW,MAAM,UACjB,UAAU,sCAAsC,uBAAuB;AAAA;AAAA,IAE/E,GACF,IAEA,qBAAC,SAAI,WAAU,uBACZ;AAAA,cAAQ,MAAM,SACb,oBAAC,SAAI,WAAU,uFACZ;AAAA,QACC;AAAA,QACA;AAAA,QACA,EAAE,OAAO,MAAM,OAAO;AAAA,MACxB,GACF,IACE;AAAA,MAEJ,oBAAC,SAAI,WAAU,2DACZ,iBAAO,WAAW,IACjB,oBAAC,SAAI,WAAU,8GACb,8BAAC,UAAK,WAAU,iCACb,oBAAU,qCAAqC,iDAAiD,GACnG,GACF,IAEA,OAAO,IAAI,CAAC,UAAU;AACpB,cAAM,WAAW,MAAM,SAAS;AAChC,cAAM,YAAY,aAAa,IAAI,QAAQ,KAAK,CAAC;AACjD,cAAM,kBAAkB,UAAU,WAAW,MAAM;AACnD,cAAM,WAAW,eAAe,MAAM;AACtC,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,WAAW,kJACT,WAAW,wBAAwB,EACrC;AAAA,YACA,YAAY,eAAe,MAAM,EAAE;AAAA,YACnC,QAAQ,WAAW,KAAK;AAAA,YAEvB;AAAA,+BAAiB,OAAO,UAAU,MAAM;AAAA,cACzC,oBAAC,SAAI,WAAU,wDACZ,0BAAgB,WAAW,IAC1B,oBAAC,SAAI,WAAU,2GACZ,oBAAU,sCAAsC,6BAA6B,GAChF,IAEA,gBAAgB,IAAI,CAAC,SAAS;AAC5B,sBAAM,aAAa,eAAe,KAAK,MACjC,kBAAkB,KAAK,MAAM,oBAAoB;AACvD,sBAAM,aAAa;AAAA,kBACjB,KAAK;AAAA,kBACL,KAAK;AAAA,kBACL,UAAU,gCAAgC,mBAAmB;AAAA,gBAC/D;AACA,sBAAM,mBAAmB;AAAA,kBACvB,KAAK;AAAA,kBACL,UAAU,0CAA0C,KAAK;AAAA,gBAC3D;AACA,sBAAM,gBAAgB,KAAK,kBACvB,cAAc,OAAO,IAAI,KAAK,KAAK,eAAe,CAAC,IACnD,UAAU,4CAA4C,SAAS;AACnE,uBACE;AAAA,kBAAC;AAAA;AAAA,oBAEC,WAAW,gHACT,aAAa,eAAe,iBAC9B;AAAA,oBACA,WAAS;AAAA,oBACT,aAAa,CAAC,UAAU;AACtB,4BAAM,aAAa,gBAAgB;AACnC,4BAAM,aAAa,QAAQ,cAAc,KAAK,EAAE;AAChD,sCAAgB,KAAK,EAAE;AAAA,oBACzB;AAAA,oBACA,WAAW;AAAA,oBAEX;AAAA,2CAAC,SAAI,WAAU,0CACb;AAAA,6CAAC,SAAI,WAAU,iBACb;AAAA,8CAAC,UAAK,WAAU,oDACb,eAAK,OACR;AAAA,0BACC,KAAK,SACJ,oBAAC,UAAK,WAAU,yDACb,eAAK,QACR,IACE;AAAA,2BACN;AAAA,wBACC,kBAAkB,KAAK,MAAM,oBAAoB,YAChD,oBAAC,WAAQ,WAAU,UAAS,IAC1B;AAAA,yBACN;AAAA,sBACA,qBAAC,SAAI,WAAU,qDACb;AAAA,6CAAC,SAAI,WAAU,2CACb;AAAA,8CAAC,UAAM,oBAAU,uCAAuC,OAAO,GAAE;AAAA,0BACjE,oBAAC,UAAK,WAAU,+BAA+B,sBAAW;AAAA,2BAC5D;AAAA,wBACA,qBAAC,SAAI,WAAU,2CACb;AAAA,8CAAC,UAAM,oBAAU,6CAA6C,aAAa,GAAE;AAAA,0BAC7E,oBAAC,UAAK,WAAU,+BAA+B,4BAAiB;AAAA,2BAClE;AAAA,wBACA,qBAAC,SAAI,WAAU,2CACb;AAAA,8CAAC,UAAM,oBAAU,+CAA+C,gBAAgB,GAAE;AAAA,0BAClF,oBAAC,UAAK,WAAU,+BAA+B,yBAAc;AAAA,2BAC/D;AAAA,yBACF;AAAA,sBACA,oBAAC,SAAI,WAAU,qCACb;AAAA,wBAAC;AAAA;AAAA,0BACC,MAAM,4BAA4B,KAAK,EAAE;AAAA,0BACzC,WAAU;AAAA,0BACV,WAAW;AAAA,0BACX,SAAS;AAAA,0BAER,oBAAU,6CAA6C,WAAW;AAAA;AAAA,sBACrE,GACF;AAAA,sBACC,KAAK,OAAO,SACX,oBAAC,SAAI,WAAU,wBACZ,eAAK,OAAO,IAAI,CAAC,WAChB;AAAA,wBAAC;AAAA;AAAA,0BAEC,WAAU;AAAA,0BACV,MAAM,gCAAgC,OAAO,EAAE;AAAA,0BAC/C,WAAW;AAAA,0BACX,SAAS;AAAA,0BAER,iBAAO;AAAA;AAAA,wBANH,OAAO;AAAA,sBAOd,CACD,GACH,IACE;AAAA,sBACH,KAAK,UAAU,SACd,oBAAC,SAAI,WAAU,wBACZ,eAAK,UAAU,IAAI,CAAC,YACnB;AAAA,wBAAC;AAAA;AAAA,0BAEC,WAAU;AAAA,0BACV,MAAM,mCAAmC,QAAQ,EAAE;AAAA,0BACnD,WAAW;AAAA,0BACX,SAAS;AAAA,0BAER,kBAAQ;AAAA;AAAA,wBANJ,QAAQ;AAAA,sBAOf,CACD,GACH,IACE;AAAA;AAAA;AAAA,kBAhFC,KAAK;AAAA,gBAiFZ;AAAA,cAEJ,CAAC,GAEL;AAAA;AAAA;AAAA,UApHK,MAAM;AAAA,QAqHb;AAAA,MAEJ,CAAC,GAEL;AAAA,OACF;AAAA,KAEJ,GACF,GACF;AAEJ;",
|
|
6
6
|
"names": ["at", "bt", "deals", "total"]
|
|
7
7
|
}
|
|
@@ -6,6 +6,13 @@ import { usePathname, useSearchParams } from "next/navigation";
|
|
|
6
6
|
import { Plus, Settings } from "lucide-react";
|
|
7
7
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
8
8
|
import { Input } from "@open-mercato/ui/primitives/input";
|
|
9
|
+
import {
|
|
10
|
+
Select,
|
|
11
|
+
SelectContent,
|
|
12
|
+
SelectItem,
|
|
13
|
+
SelectTrigger,
|
|
14
|
+
SelectValue
|
|
15
|
+
} from "@open-mercato/ui/primitives/select";
|
|
9
16
|
import {
|
|
10
17
|
Dialog,
|
|
11
18
|
DialogContent,
|
|
@@ -117,16 +124,26 @@ function AddressEditor({
|
|
|
117
124
|
),
|
|
118
125
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
119
126
|
/* @__PURE__ */ jsxs(
|
|
120
|
-
|
|
127
|
+
Select,
|
|
121
128
|
{
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
onChange: (evt) => update("purpose", evt.target.value),
|
|
129
|
+
value: current.purpose || void 0,
|
|
130
|
+
onValueChange: (next) => update("purpose", next ?? ""),
|
|
125
131
|
disabled,
|
|
126
|
-
"aria-invalid": errors.purpose ? "true" : void 0,
|
|
127
132
|
children: [
|
|
128
|
-
/* @__PURE__ */ jsx(
|
|
129
|
-
|
|
133
|
+
/* @__PURE__ */ jsx(
|
|
134
|
+
SelectTrigger,
|
|
135
|
+
{
|
|
136
|
+
className: errors.purpose ? "border-destructive" : void 0,
|
|
137
|
+
"aria-invalid": errors.purpose ? "true" : void 0,
|
|
138
|
+
children: /* @__PURE__ */ jsx(
|
|
139
|
+
SelectValue,
|
|
140
|
+
{
|
|
141
|
+
placeholder: addressTypesLoading ? t("customers.people.detail.addresses.types.loading", "Loading\u2026") : t("customers.people.detail.addresses.types.placeholder", "Address type")
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
),
|
|
146
|
+
/* @__PURE__ */ jsx(SelectContent, { children: addressTypes.map((entry) => /* @__PURE__ */ jsx(SelectItem, { value: entry.value, children: entry.label }, entry.value)) })
|
|
130
147
|
]
|
|
131
148
|
}
|
|
132
149
|
),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/customers/components/AddressEditor.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { usePathname, useSearchParams } from 'next/navigation'\nimport { Plus, Settings } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@open-mercato/ui/primitives/dialog'\nimport { buildCountryOptions } from '@open-mercato/shared/lib/location/countries'\nimport { buildHrefWithReturnTo } from '@open-mercato/shared/lib/navigation/returnTo'\nimport type { AddressFormatStrategy } from '../utils/addressFormat'\nimport { useAddressTypes } from './detail/hooks/useAddressTypes'\n\ntype Translator = (key: string, fallback?: string, params?: Record<string, string | number>) => string\n\nexport type AddressEditorDraft = {\n name: string\n purpose: string\n companyName: string\n addressLine1: string\n addressLine2: string\n buildingNumber: string\n flatNumber: string\n city: string\n region: string\n postalCode: string\n country: string\n isPrimary: boolean\n}\n\nexport type AddressEditorField =\n | 'name'\n | 'purpose'\n | 'companyName'\n | 'addressLine1'\n | 'addressLine2'\n | 'buildingNumber'\n | 'flatNumber'\n | 'city'\n | 'region'\n | 'postalCode'\n | 'country'\n | 'isPrimary'\n\ntype AddressEditorProps = {\n value: AddressEditorDraft\n onChange: (next: AddressEditorDraft) => void\n format: AddressFormatStrategy\n t: Translator\n disabled?: boolean\n errors?: Partial<Record<AddressEditorField, string>>\n hidePrimaryToggle?: boolean\n showFormatHint?: boolean\n}\n\nexport function AddressEditor({\n value,\n onChange,\n format,\n t,\n disabled = false,\n errors = {},\n hidePrimaryToggle = false,\n showFormatHint = true,\n}: AddressEditorProps) {\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const { options: addressTypes, loading: addressTypesLoading, error: addressTypeError, createType } = useAddressTypes(t)\n const [typeDialogOpen, setTypeDialogOpen] = React.useState(false)\n const [typeValue, setTypeValue] = React.useState('')\n const [typeFormError, setTypeFormError] = React.useState<string | null>(null)\n const [countryDialogOpen, setCountryDialogOpen] = React.useState(false)\n const [countryQuery, setCountryQuery] = React.useState('')\n\n const countryOptions = React.useMemo(\n () =>\n buildCountryOptions({\n transformLabel: (code, fallback) => t(`customers.countries.${code.toLowerCase()}`, fallback ?? code),\n }),\n [t],\n )\n\n const current: AddressEditorDraft = {\n name: value.name ?? '',\n purpose: value.purpose ?? '',\n companyName: value.companyName ?? '',\n addressLine1: value.addressLine1 ?? '',\n addressLine2: value.addressLine2 ?? '',\n buildingNumber: value.buildingNumber ?? '',\n flatNumber: value.flatNumber ?? '',\n city: value.city ?? '',\n region: value.region ?? '',\n postalCode: value.postalCode ?? '',\n country: value.country ?? '',\n isPrimary: value.isPrimary ?? false,\n }\n\n const update = React.useCallback(\n (key: keyof AddressEditorDraft, nextValue: string | boolean) => {\n onChange({ ...current, [key]: nextValue })\n },\n [current, onChange],\n )\n\n const filteredCountryOptions = React.useMemo(() => {\n const query = countryQuery.trim().toLowerCase()\n if (!query.length) return countryOptions\n return countryOptions.filter(\n (option) => option.label.toLowerCase().includes(query) || option.code.toLowerCase().includes(query),\n )\n }, [countryOptions, countryQuery])\n\n const selectedCountry = React.useMemo(() => {\n const code = (current.country ?? '').toUpperCase()\n if (!code.length) return null\n return countryOptions.find((option) => option.code === code) ?? null\n }, [countryOptions, current.country])\n const returnTo = React.useMemo(() => {\n const query = searchParams?.toString() ?? ''\n if (!pathname) return null\n return query.length ? `${pathname}?${query}` : pathname\n }, [pathname, searchParams])\n const manageAddressTypesHref = React.useMemo(\n () => buildHrefWithReturnTo('/backend/config/customers', returnTo),\n [returnTo],\n )\n\n const handleTypeSubmit = React.useCallback(\n async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n const trimmed = typeValue.trim()\n if (!trimmed.length) {\n setTypeFormError(t('customers.people.detail.addresses.types.emptyError', 'Please provide a value'))\n return\n }\n setTypeFormError(null)\n await createType(trimmed)\n setTypeDialogOpen(false)\n setTypeValue('')\n },\n [createType, t, typeValue],\n )\n\n const inputClass = (field: AddressEditorField) =>\n [\n 'w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring',\n errors[field] ? 'border-status-error-border focus:ring-status-error-border' : 'border-input bg-background',\n ].join(' ')\n\n return (\n <div className=\"space-y-3\">\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <Input\n className={inputClass('name')}\n placeholder={t('customers.people.detail.addresses.fields.label', 'Label')}\n value={current.name}\n onChange={(evt) => update('name', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.name ? 'true' : undefined}\n />\n <div className=\"flex gap-2\">\n <select\n className={inputClass('purpose')}\n value={current.purpose}\n onChange={(evt) => update('purpose', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.purpose ? 'true' : undefined}\n >\n <option value=\"\">\n {addressTypesLoading\n ? t('customers.people.detail.addresses.types.loading', 'Loading\u2026')\n : t('customers.people.detail.addresses.types.placeholder', 'Address type')}\n </option>\n {addressTypes.map((entry) => (\n <option key={entry.value} value={entry.value}>\n {entry.label}\n </option>\n ))}\n </select>\n <Dialog open={typeDialogOpen} onOpenChange={setTypeDialogOpen}>\n <DialogTrigger asChild>\n <Button type=\"button\" variant=\"outline\" size=\"icon\" className=\"shrink-0\" disabled={disabled}>\n <Plus className=\"h-4 w-4\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t('customers.people.detail.addresses.types.add', 'Add address type')}</DialogTitle>\n <DialogDescription>\n {t('customers.people.detail.addresses.types.addHint', 'Create a new address type for reuse.')}\n </DialogDescription>\n </DialogHeader>\n <form className=\"space-y-3\" onSubmit={handleTypeSubmit}>\n <Input\n autoFocus\n value={typeValue}\n onChange={(evt) => {\n setTypeValue(evt.target.value)\n if (typeFormError) setTypeFormError(null)\n }}\n placeholder={t('customers.people.detail.addresses.types.placeholder', 'Address type')}\n disabled={disabled}\n aria-invalid={typeFormError ? 'true' : undefined}\n />\n {typeFormError ? <p className=\"text-sm text-destructive\">{typeFormError}</p> : null}\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setTypeDialogOpen(false)} disabled={disabled}>\n {t('customers.people.detail.addresses.types.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={disabled || !typeValue.trim()}>\n {t('customers.people.detail.addresses.types.save', 'Save')}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n <Button\n asChild\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"shrink-0\"\n disabled={disabled}\n title={t('customers.people.detail.addresses.types.manage', 'Manage address types')}\n >\n <Link\n href={manageAddressTypesHref}\n aria-label={t('customers.people.detail.addresses.types.manage', 'Manage address types')}\n >\n <Settings className=\"h-4 w-4\" />\n </Link>\n </Button>\n </div>\n </div>\n {errors.purpose ? <p className=\"text-xs text-destructive\">{errors.purpose}</p> : null}\n {addressTypeError ? <p className=\"text-xs text-destructive\">{addressTypeError}</p> : null}\n <Input\n className={inputClass('companyName')}\n placeholder={t('customers.people.detail.addresses.fields.companyName', 'Company name')}\n value={current.companyName}\n onChange={(evt) => update('companyName', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.companyName ? 'true' : undefined}\n />\n {showFormatHint ? (\n <p className=\"text-xs text-muted-foreground\">\n {format === 'street_first'\n ? t('customers.people.detail.addresses.streetFormatHint', 'Street-first layout is active.')\n : t('customers.people.detail.addresses.lineFormatHint', 'Address-line layout is active.')}\n </p>\n ) : null}\n <div className=\"grid grid-cols-1 gap-2 sm:grid-cols-2\">\n <Input\n className={inputClass('addressLine1')}\n placeholder={\n format === 'street_first'\n ? t('customers.people.detail.addresses.fields.street', 'Street')\n : t('customers.people.detail.addresses.fields.line1', 'Address line 1')\n }\n value={current.addressLine1}\n onChange={(evt) => update('addressLine1', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.addressLine1 ? 'true' : undefined}\n />\n {errors.addressLine1 ? <p className=\"text-xs text-destructive sm:col-span-2\">{errors.addressLine1}</p> : null}\n {format === 'street_first' ? (\n <>\n <Input\n className={inputClass('buildingNumber')}\n placeholder={t('customers.people.detail.addresses.fields.buildingNumber', 'Building number')}\n value={current.buildingNumber}\n onChange={(evt) => update('buildingNumber', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.buildingNumber ? 'true' : undefined}\n />\n <Input\n className={inputClass('flatNumber')}\n placeholder={t('customers.people.detail.addresses.fields.flatNumber', 'Flat number')}\n value={current.flatNumber}\n onChange={(evt) => update('flatNumber', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.flatNumber ? 'true' : undefined}\n />\n <Input\n className={inputClass('addressLine2')}\n placeholder={t('customers.people.detail.addresses.fields.streetExtra', 'Address line 2')}\n value={current.addressLine2}\n onChange={(evt) => update('addressLine2', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.addressLine2 ? 'true' : undefined}\n />\n {errors.addressLine2 ? <p className=\"text-xs text-destructive sm:col-span-2\">{errors.addressLine2}</p> : null}\n </>\n ) : (\n <>\n <Input\n className={inputClass('addressLine2')}\n placeholder={t('customers.people.detail.addresses.fields.line2', 'Address line 2')}\n value={current.addressLine2}\n onChange={(evt) => update('addressLine2', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.addressLine2 ? 'true' : undefined}\n />\n {errors.addressLine2 ? <p className=\"text-xs text-destructive sm:col-span-2\">{errors.addressLine2}</p> : null}\n </>\n )}\n <Input\n className={inputClass('city')}\n placeholder={t('customers.people.detail.addresses.fields.city', 'City')}\n value={current.city}\n onChange={(evt) => update('city', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.city ? 'true' : undefined}\n />\n {errors.city ? <p className=\"text-xs text-destructive\">{errors.city}</p> : null}\n <Input\n className={inputClass('region')}\n placeholder={t('customers.people.detail.addresses.fields.region', 'Region/state')}\n value={current.region}\n onChange={(evt) => update('region', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.region ? 'true' : undefined}\n />\n {errors.region ? <p className=\"text-xs text-destructive\">{errors.region}</p> : null}\n <Input\n className={inputClass('postalCode')}\n placeholder={t('customers.people.detail.addresses.fields.postalCode', 'Postal code')}\n value={current.postalCode}\n onChange={(evt) => update('postalCode', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.postalCode ? 'true' : undefined}\n />\n {errors.postalCode ? <p className=\"text-xs text-destructive\">{errors.postalCode}</p> : null}\n <Dialog\n open={countryDialogOpen}\n onOpenChange={(open) => {\n setCountryDialogOpen(open)\n if (!open) setCountryQuery('')\n }}\n >\n <DialogTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n className={`${inputClass('country')} h-10 w-full justify-between`}\n disabled={disabled}\n aria-invalid={errors.country ? 'true' : undefined}\n >\n <span className=\"truncate text-left\">\n {selectedCountry\n ? `${selectedCountry.label}`\n : t('customers.people.detail.addresses.countryPlaceholder', 'Select country')}\n </span>\n <span className=\"ml-2 text-xs text-muted-foreground\">\n {selectedCountry ? selectedCountry.code : null}\n </span>\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t('customers.people.detail.addresses.countryDialogTitle', 'Select country')}</DialogTitle>\n <DialogDescription>\n {t('customers.people.detail.addresses.countryDialogDescription', 'Search and choose an ISO country code.')}\n </DialogDescription>\n </DialogHeader>\n <div className=\"space-y-3\">\n <Input\n placeholder={t('customers.people.detail.addresses.countrySearch', 'Search country')}\n value={countryQuery}\n onChange={(evt) => setCountryQuery(evt.target.value)}\n />\n <div className=\"max-h-64 overflow-y-auto rounded border divide-y\">\n {filteredCountryOptions.length === 0 ? (\n <p className=\"px-3 py-2 text-sm text-muted-foreground\">\n {t('customers.people.detail.addresses.countryEmpty', 'No matches found')}\n </p>\n ) : (\n filteredCountryOptions.map((option) => (\n <Button\n key={option.code}\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-full justify-between rounded-none font-normal\"\n onClick={() => {\n update('country', option.code)\n setCountryDialogOpen(false)\n setCountryQuery('')\n }}\n >\n <span className=\"truncate\">{option.label}</span>\n <span className=\"text-xs text-muted-foreground\">{option.code}</span>\n </Button>\n ))\n )}\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => {\n update('country', '')\n setCountryDialogOpen(false)\n setCountryQuery('')\n }}\n disabled={disabled}\n >\n {t('customers.people.detail.addresses.countryClear', 'Clear')}\n </Button>\n <Button type=\"button\" onClick={() => setCountryDialogOpen(false)}>\n {t('customers.people.detail.addresses.countryClose', 'Done')}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n {errors.country ? <p className=\"text-xs text-destructive\">{errors.country}</p> : null}\n </div>\n {!hidePrimaryToggle ? (\n <label className=\"inline-flex items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={current.isPrimary}\n onChange={(evt) => update('isPrimary', evt.target.checked)}\n disabled={disabled}\n aria-invalid={errors.isPrimary ? 'true' : undefined}\n />\n <span>{t('customers.people.detail.addresses.fields.primary', 'Set as primary')}</span>\n </label>\n ) : null}\n {errors.isPrimary ? <p className=\"text-xs text-destructive\">{errors.isPrimary}</p> : null}\n </div>\n )\n}\n\nexport default AddressEditor\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { usePathname, useSearchParams } from 'next/navigation'\nimport { Plus, Settings } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '@open-mercato/ui/primitives/dialog'\nimport { buildCountryOptions } from '@open-mercato/shared/lib/location/countries'\nimport { buildHrefWithReturnTo } from '@open-mercato/shared/lib/navigation/returnTo'\nimport type { AddressFormatStrategy } from '../utils/addressFormat'\nimport { useAddressTypes } from './detail/hooks/useAddressTypes'\n\ntype Translator = (key: string, fallback?: string, params?: Record<string, string | number>) => string\n\nexport type AddressEditorDraft = {\n name: string\n purpose: string\n companyName: string\n addressLine1: string\n addressLine2: string\n buildingNumber: string\n flatNumber: string\n city: string\n region: string\n postalCode: string\n country: string\n isPrimary: boolean\n}\n\nexport type AddressEditorField =\n | 'name'\n | 'purpose'\n | 'companyName'\n | 'addressLine1'\n | 'addressLine2'\n | 'buildingNumber'\n | 'flatNumber'\n | 'city'\n | 'region'\n | 'postalCode'\n | 'country'\n | 'isPrimary'\n\ntype AddressEditorProps = {\n value: AddressEditorDraft\n onChange: (next: AddressEditorDraft) => void\n format: AddressFormatStrategy\n t: Translator\n disabled?: boolean\n errors?: Partial<Record<AddressEditorField, string>>\n hidePrimaryToggle?: boolean\n showFormatHint?: boolean\n}\n\nexport function AddressEditor({\n value,\n onChange,\n format,\n t,\n disabled = false,\n errors = {},\n hidePrimaryToggle = false,\n showFormatHint = true,\n}: AddressEditorProps) {\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const { options: addressTypes, loading: addressTypesLoading, error: addressTypeError, createType } = useAddressTypes(t)\n const [typeDialogOpen, setTypeDialogOpen] = React.useState(false)\n const [typeValue, setTypeValue] = React.useState('')\n const [typeFormError, setTypeFormError] = React.useState<string | null>(null)\n const [countryDialogOpen, setCountryDialogOpen] = React.useState(false)\n const [countryQuery, setCountryQuery] = React.useState('')\n\n const countryOptions = React.useMemo(\n () =>\n buildCountryOptions({\n transformLabel: (code, fallback) => t(`customers.countries.${code.toLowerCase()}`, fallback ?? code),\n }),\n [t],\n )\n\n const current: AddressEditorDraft = {\n name: value.name ?? '',\n purpose: value.purpose ?? '',\n companyName: value.companyName ?? '',\n addressLine1: value.addressLine1 ?? '',\n addressLine2: value.addressLine2 ?? '',\n buildingNumber: value.buildingNumber ?? '',\n flatNumber: value.flatNumber ?? '',\n city: value.city ?? '',\n region: value.region ?? '',\n postalCode: value.postalCode ?? '',\n country: value.country ?? '',\n isPrimary: value.isPrimary ?? false,\n }\n\n const update = React.useCallback(\n (key: keyof AddressEditorDraft, nextValue: string | boolean) => {\n onChange({ ...current, [key]: nextValue })\n },\n [current, onChange],\n )\n\n const filteredCountryOptions = React.useMemo(() => {\n const query = countryQuery.trim().toLowerCase()\n if (!query.length) return countryOptions\n return countryOptions.filter(\n (option) => option.label.toLowerCase().includes(query) || option.code.toLowerCase().includes(query),\n )\n }, [countryOptions, countryQuery])\n\n const selectedCountry = React.useMemo(() => {\n const code = (current.country ?? '').toUpperCase()\n if (!code.length) return null\n return countryOptions.find((option) => option.code === code) ?? null\n }, [countryOptions, current.country])\n const returnTo = React.useMemo(() => {\n const query = searchParams?.toString() ?? ''\n if (!pathname) return null\n return query.length ? `${pathname}?${query}` : pathname\n }, [pathname, searchParams])\n const manageAddressTypesHref = React.useMemo(\n () => buildHrefWithReturnTo('/backend/config/customers', returnTo),\n [returnTo],\n )\n\n const handleTypeSubmit = React.useCallback(\n async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n const trimmed = typeValue.trim()\n if (!trimmed.length) {\n setTypeFormError(t('customers.people.detail.addresses.types.emptyError', 'Please provide a value'))\n return\n }\n setTypeFormError(null)\n await createType(trimmed)\n setTypeDialogOpen(false)\n setTypeValue('')\n },\n [createType, t, typeValue],\n )\n\n const inputClass = (field: AddressEditorField) =>\n [\n 'w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring',\n errors[field] ? 'border-status-error-border focus:ring-status-error-border' : 'border-input bg-background',\n ].join(' ')\n\n return (\n <div className=\"space-y-3\">\n <div className=\"grid gap-2 sm:grid-cols-2\">\n <Input\n className={inputClass('name')}\n placeholder={t('customers.people.detail.addresses.fields.label', 'Label')}\n value={current.name}\n onChange={(evt) => update('name', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.name ? 'true' : undefined}\n />\n <div className=\"flex gap-2\">\n <Select\n value={current.purpose || undefined}\n onValueChange={(next) => update('purpose', next ?? '')}\n disabled={disabled}\n >\n <SelectTrigger\n className={errors.purpose ? 'border-destructive' : undefined}\n aria-invalid={errors.purpose ? 'true' : undefined}\n >\n <SelectValue\n placeholder={\n addressTypesLoading\n ? t('customers.people.detail.addresses.types.loading', 'Loading\u2026')\n : t('customers.people.detail.addresses.types.placeholder', 'Address type')\n }\n />\n </SelectTrigger>\n <SelectContent>\n {addressTypes.map((entry) => (\n <SelectItem key={entry.value} value={entry.value}>\n {entry.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Dialog open={typeDialogOpen} onOpenChange={setTypeDialogOpen}>\n <DialogTrigger asChild>\n <Button type=\"button\" variant=\"outline\" size=\"icon\" className=\"shrink-0\" disabled={disabled}>\n <Plus className=\"h-4 w-4\" />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t('customers.people.detail.addresses.types.add', 'Add address type')}</DialogTitle>\n <DialogDescription>\n {t('customers.people.detail.addresses.types.addHint', 'Create a new address type for reuse.')}\n </DialogDescription>\n </DialogHeader>\n <form className=\"space-y-3\" onSubmit={handleTypeSubmit}>\n <Input\n autoFocus\n value={typeValue}\n onChange={(evt) => {\n setTypeValue(evt.target.value)\n if (typeFormError) setTypeFormError(null)\n }}\n placeholder={t('customers.people.detail.addresses.types.placeholder', 'Address type')}\n disabled={disabled}\n aria-invalid={typeFormError ? 'true' : undefined}\n />\n {typeFormError ? <p className=\"text-sm text-destructive\">{typeFormError}</p> : null}\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setTypeDialogOpen(false)} disabled={disabled}>\n {t('customers.people.detail.addresses.types.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={disabled || !typeValue.trim()}>\n {t('customers.people.detail.addresses.types.save', 'Save')}\n </Button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n <Button\n asChild\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"shrink-0\"\n disabled={disabled}\n title={t('customers.people.detail.addresses.types.manage', 'Manage address types')}\n >\n <Link\n href={manageAddressTypesHref}\n aria-label={t('customers.people.detail.addresses.types.manage', 'Manage address types')}\n >\n <Settings className=\"h-4 w-4\" />\n </Link>\n </Button>\n </div>\n </div>\n {errors.purpose ? <p className=\"text-xs text-destructive\">{errors.purpose}</p> : null}\n {addressTypeError ? <p className=\"text-xs text-destructive\">{addressTypeError}</p> : null}\n <Input\n className={inputClass('companyName')}\n placeholder={t('customers.people.detail.addresses.fields.companyName', 'Company name')}\n value={current.companyName}\n onChange={(evt) => update('companyName', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.companyName ? 'true' : undefined}\n />\n {showFormatHint ? (\n <p className=\"text-xs text-muted-foreground\">\n {format === 'street_first'\n ? t('customers.people.detail.addresses.streetFormatHint', 'Street-first layout is active.')\n : t('customers.people.detail.addresses.lineFormatHint', 'Address-line layout is active.')}\n </p>\n ) : null}\n <div className=\"grid grid-cols-1 gap-2 sm:grid-cols-2\">\n <Input\n className={inputClass('addressLine1')}\n placeholder={\n format === 'street_first'\n ? t('customers.people.detail.addresses.fields.street', 'Street')\n : t('customers.people.detail.addresses.fields.line1', 'Address line 1')\n }\n value={current.addressLine1}\n onChange={(evt) => update('addressLine1', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.addressLine1 ? 'true' : undefined}\n />\n {errors.addressLine1 ? <p className=\"text-xs text-destructive sm:col-span-2\">{errors.addressLine1}</p> : null}\n {format === 'street_first' ? (\n <>\n <Input\n className={inputClass('buildingNumber')}\n placeholder={t('customers.people.detail.addresses.fields.buildingNumber', 'Building number')}\n value={current.buildingNumber}\n onChange={(evt) => update('buildingNumber', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.buildingNumber ? 'true' : undefined}\n />\n <Input\n className={inputClass('flatNumber')}\n placeholder={t('customers.people.detail.addresses.fields.flatNumber', 'Flat number')}\n value={current.flatNumber}\n onChange={(evt) => update('flatNumber', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.flatNumber ? 'true' : undefined}\n />\n <Input\n className={inputClass('addressLine2')}\n placeholder={t('customers.people.detail.addresses.fields.streetExtra', 'Address line 2')}\n value={current.addressLine2}\n onChange={(evt) => update('addressLine2', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.addressLine2 ? 'true' : undefined}\n />\n {errors.addressLine2 ? <p className=\"text-xs text-destructive sm:col-span-2\">{errors.addressLine2}</p> : null}\n </>\n ) : (\n <>\n <Input\n className={inputClass('addressLine2')}\n placeholder={t('customers.people.detail.addresses.fields.line2', 'Address line 2')}\n value={current.addressLine2}\n onChange={(evt) => update('addressLine2', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.addressLine2 ? 'true' : undefined}\n />\n {errors.addressLine2 ? <p className=\"text-xs text-destructive sm:col-span-2\">{errors.addressLine2}</p> : null}\n </>\n )}\n <Input\n className={inputClass('city')}\n placeholder={t('customers.people.detail.addresses.fields.city', 'City')}\n value={current.city}\n onChange={(evt) => update('city', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.city ? 'true' : undefined}\n />\n {errors.city ? <p className=\"text-xs text-destructive\">{errors.city}</p> : null}\n <Input\n className={inputClass('region')}\n placeholder={t('customers.people.detail.addresses.fields.region', 'Region/state')}\n value={current.region}\n onChange={(evt) => update('region', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.region ? 'true' : undefined}\n />\n {errors.region ? <p className=\"text-xs text-destructive\">{errors.region}</p> : null}\n <Input\n className={inputClass('postalCode')}\n placeholder={t('customers.people.detail.addresses.fields.postalCode', 'Postal code')}\n value={current.postalCode}\n onChange={(evt) => update('postalCode', evt.target.value)}\n disabled={disabled}\n aria-invalid={errors.postalCode ? 'true' : undefined}\n />\n {errors.postalCode ? <p className=\"text-xs text-destructive\">{errors.postalCode}</p> : null}\n <Dialog\n open={countryDialogOpen}\n onOpenChange={(open) => {\n setCountryDialogOpen(open)\n if (!open) setCountryQuery('')\n }}\n >\n <DialogTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n className={`${inputClass('country')} h-10 w-full justify-between`}\n disabled={disabled}\n aria-invalid={errors.country ? 'true' : undefined}\n >\n <span className=\"truncate text-left\">\n {selectedCountry\n ? `${selectedCountry.label}`\n : t('customers.people.detail.addresses.countryPlaceholder', 'Select country')}\n </span>\n <span className=\"ml-2 text-xs text-muted-foreground\">\n {selectedCountry ? selectedCountry.code : null}\n </span>\n </Button>\n </DialogTrigger>\n <DialogContent className=\"sm:max-w-md\">\n <DialogHeader>\n <DialogTitle>{t('customers.people.detail.addresses.countryDialogTitle', 'Select country')}</DialogTitle>\n <DialogDescription>\n {t('customers.people.detail.addresses.countryDialogDescription', 'Search and choose an ISO country code.')}\n </DialogDescription>\n </DialogHeader>\n <div className=\"space-y-3\">\n <Input\n placeholder={t('customers.people.detail.addresses.countrySearch', 'Search country')}\n value={countryQuery}\n onChange={(evt) => setCountryQuery(evt.target.value)}\n />\n <div className=\"max-h-64 overflow-y-auto rounded border divide-y\">\n {filteredCountryOptions.length === 0 ? (\n <p className=\"px-3 py-2 text-sm text-muted-foreground\">\n {t('customers.people.detail.addresses.countryEmpty', 'No matches found')}\n </p>\n ) : (\n filteredCountryOptions.map((option) => (\n <Button\n key={option.code}\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-full justify-between rounded-none font-normal\"\n onClick={() => {\n update('country', option.code)\n setCountryDialogOpen(false)\n setCountryQuery('')\n }}\n >\n <span className=\"truncate\">{option.label}</span>\n <span className=\"text-xs text-muted-foreground\">{option.code}</span>\n </Button>\n ))\n )}\n </div>\n <div className=\"flex items-center justify-between gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => {\n update('country', '')\n setCountryDialogOpen(false)\n setCountryQuery('')\n }}\n disabled={disabled}\n >\n {t('customers.people.detail.addresses.countryClear', 'Clear')}\n </Button>\n <Button type=\"button\" onClick={() => setCountryDialogOpen(false)}>\n {t('customers.people.detail.addresses.countryClose', 'Done')}\n </Button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n {errors.country ? <p className=\"text-xs text-destructive\">{errors.country}</p> : null}\n </div>\n {!hidePrimaryToggle ? (\n <label className=\"inline-flex items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={current.isPrimary}\n onChange={(evt) => update('isPrimary', evt.target.checked)}\n disabled={disabled}\n aria-invalid={errors.isPrimary ? 'true' : undefined}\n />\n <span>{t('customers.people.detail.addresses.fields.primary', 'Set as primary')}</span>\n </label>\n ) : null}\n {errors.isPrimary ? <p className=\"text-xs text-destructive\">{errors.isPrimary}</p> : null}\n </div>\n )\n}\n\nexport default AddressEditor\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { Spinner } from "@open-mercato/ui/primitives/spinner";
|
|
5
|
+
import { RadioGroup, Radio } from "@open-mercato/ui/primitives/radio";
|
|
5
6
|
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
6
7
|
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
7
8
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
@@ -108,31 +109,40 @@ function AddressFormatSettings() {
|
|
|
108
109
|
loading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
109
110
|
/* @__PURE__ */ jsx(Spinner, { className: "h-4 w-4" }),
|
|
110
111
|
t("customers.config.addressFormat.loading", "Loading current preference\u2026")
|
|
111
|
-
] }) : /* @__PURE__ */ jsxs(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
112
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
113
|
+
RadioGroup,
|
|
114
|
+
{
|
|
115
|
+
className: "space-y-3",
|
|
116
|
+
value: format,
|
|
117
|
+
onValueChange: (next) => handleChange(next),
|
|
118
|
+
name: "address-format",
|
|
119
|
+
children: [
|
|
120
|
+
options.map((option) => {
|
|
121
|
+
const inputId = `address-format-${option.id}`;
|
|
122
|
+
return /* @__PURE__ */ jsxs("label", { htmlFor: inputId, className: "flex cursor-pointer items-start gap-3 rounded border p-3", children: [
|
|
123
|
+
/* @__PURE__ */ jsx(
|
|
124
|
+
Radio,
|
|
125
|
+
{
|
|
126
|
+
id: inputId,
|
|
127
|
+
className: "mt-1",
|
|
128
|
+
value: option.id,
|
|
129
|
+
disabled: pending !== null && pending !== option.id
|
|
130
|
+
}
|
|
131
|
+
),
|
|
132
|
+
/* @__PURE__ */ jsxs("span", { className: "space-y-1", children: [
|
|
133
|
+
/* @__PURE__ */ jsx("span", { className: "block text-sm font-medium", children: option.title }),
|
|
134
|
+
/* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground", children: option.description })
|
|
135
|
+
] })
|
|
136
|
+
] }, option.id);
|
|
137
|
+
}),
|
|
138
|
+
error ? /* @__PURE__ */ jsx("p", { className: "text-sm text-status-error-text", children: error }) : null,
|
|
139
|
+
pending ? /* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-2 rounded border border-dashed px-3 py-1 text-xs text-muted-foreground", children: [
|
|
140
|
+
/* @__PURE__ */ jsx(Spinner, { className: "h-3 w-3" }),
|
|
141
|
+
t("customers.config.addressFormat.updating", "Saving preference\u2026")
|
|
142
|
+
] }) : null
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
)
|
|
136
146
|
] });
|
|
137
147
|
}
|
|
138
148
|
var AddressFormatSettings_default = AddressFormatSettings;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/customers/components/AddressFormatSettings.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { AddressFormatStrategy } from '../utils/addressFormat'\n\ntype Option = {\n id: AddressFormatStrategy\n title: string\n description: string\n}\n\nexport function AddressFormatSettings() {\n const t = useT()\n const [format, setFormat] = React.useState<AddressFormatStrategy>('line_first')\n const [loading, setLoading] = React.useState(true)\n const [pending, setPending] = React.useState<AddressFormatStrategy | null>(null)\n const [error, setError] = React.useState<string | null>(null)\n\n const options = React.useMemo<Option[]>(\n () => [\n {\n id: 'line_first',\n title: t('customers.config.addressFormat.lineFirstTitle', 'Address lines first'),\n description: t(\n 'customers.config.addressFormat.lineFirstDescription',\n 'Collect address line 1 and 2, then postal code, city, region, and country.'\n ),\n },\n {\n id: 'street_first',\n title: t('customers.config.addressFormat.streetFirstTitle', 'Street-first (European)'),\n description: t(\n 'customers.config.addressFormat.streetFirstDescription',\n 'Collect street, building and flat numbers before postal code, city, region, and country.'\n ),\n },\n ],\n [t]\n )\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n try {\n setLoading(true)\n const call = await apiCall<{ addressFormat?: string; error?: string }>('/api/customers/settings/address-format')\n const payload = (call.result ?? {}) as Record<string, unknown>\n if (!call.ok) {\n const message =\n typeof payload?.error === 'string'\n ? String(payload.error)\n : t('customers.config.addressFormat.error', 'Failed to load address settings')\n if (!cancelled) setError(message)\n return\n }\n const valueRaw = payload?.addressFormat\n const value = typeof valueRaw === 'string' ? valueRaw : null\n if (!cancelled && (value === 'line_first' || value === 'street_first')) {\n setFormat(value)\n }\n } catch (err) {\n if (!cancelled) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : t('customers.config.addressFormat.error', 'Failed to load address settings')\n setError(message)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n load().catch(() => {})\n return () => {\n cancelled = true\n }\n }, [t])\n\n const handleChange = React.useCallback(\n async (next: AddressFormatStrategy) => {\n if (next === format) return\n setPending(next)\n setError(null)\n try {\n const call = await apiCall<Record<string, unknown>>(\n '/api/customers/settings/address-format',\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ addressFormat: next }),\n },\n )\n const payload = call.result ?? {}\n if (!call.ok) {\n const message =\n typeof payload?.error === 'string'\n ? payload.error\n : t('customers.config.addressFormat.errorSave', 'Failed to update address settings')\n setError(message)\n flash(message, 'error')\n return\n }\n setFormat(next)\n flash(t('customers.config.addressFormat.success', 'Address format updated'), 'success')\n } catch (err) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : t('customers.config.addressFormat.errorSave', 'Failed to update address settings')\n setError(message)\n flash(message, 'error')\n } finally {\n setPending(null)\n }\n },\n [format, t]\n )\n\n return (\n <section className=\"space-y-4 rounded-lg border bg-background p-4\">\n <header className=\"space-y-1\">\n <h2 className=\"text-lg font-semibold\">\n {t('customers.config.addressFormat.title', 'Customer address format')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'customers.config.addressFormat.description',\n 'Choose how address forms and displays should be structured across the customer module.'\n )}\n </p>\n </header>\n {loading ? (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner className=\"h-4 w-4\" />\n {t('customers.config.addressFormat.loading', 'Loading current preference\u2026')}\n </div>\n ) : (\n <
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { RadioGroup, Radio } from '@open-mercato/ui/primitives/radio'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { AddressFormatStrategy } from '../utils/addressFormat'\n\ntype Option = {\n id: AddressFormatStrategy\n title: string\n description: string\n}\n\nexport function AddressFormatSettings() {\n const t = useT()\n const [format, setFormat] = React.useState<AddressFormatStrategy>('line_first')\n const [loading, setLoading] = React.useState(true)\n const [pending, setPending] = React.useState<AddressFormatStrategy | null>(null)\n const [error, setError] = React.useState<string | null>(null)\n\n const options = React.useMemo<Option[]>(\n () => [\n {\n id: 'line_first',\n title: t('customers.config.addressFormat.lineFirstTitle', 'Address lines first'),\n description: t(\n 'customers.config.addressFormat.lineFirstDescription',\n 'Collect address line 1 and 2, then postal code, city, region, and country.'\n ),\n },\n {\n id: 'street_first',\n title: t('customers.config.addressFormat.streetFirstTitle', 'Street-first (European)'),\n description: t(\n 'customers.config.addressFormat.streetFirstDescription',\n 'Collect street, building and flat numbers before postal code, city, region, and country.'\n ),\n },\n ],\n [t]\n )\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n try {\n setLoading(true)\n const call = await apiCall<{ addressFormat?: string; error?: string }>('/api/customers/settings/address-format')\n const payload = (call.result ?? {}) as Record<string, unknown>\n if (!call.ok) {\n const message =\n typeof payload?.error === 'string'\n ? String(payload.error)\n : t('customers.config.addressFormat.error', 'Failed to load address settings')\n if (!cancelled) setError(message)\n return\n }\n const valueRaw = payload?.addressFormat\n const value = typeof valueRaw === 'string' ? valueRaw : null\n if (!cancelled && (value === 'line_first' || value === 'street_first')) {\n setFormat(value)\n }\n } catch (err) {\n if (!cancelled) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : t('customers.config.addressFormat.error', 'Failed to load address settings')\n setError(message)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n load().catch(() => {})\n return () => {\n cancelled = true\n }\n }, [t])\n\n const handleChange = React.useCallback(\n async (next: AddressFormatStrategy) => {\n if (next === format) return\n setPending(next)\n setError(null)\n try {\n const call = await apiCall<Record<string, unknown>>(\n '/api/customers/settings/address-format',\n {\n method: 'PUT',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ addressFormat: next }),\n },\n )\n const payload = call.result ?? {}\n if (!call.ok) {\n const message =\n typeof payload?.error === 'string'\n ? payload.error\n : t('customers.config.addressFormat.errorSave', 'Failed to update address settings')\n setError(message)\n flash(message, 'error')\n return\n }\n setFormat(next)\n flash(t('customers.config.addressFormat.success', 'Address format updated'), 'success')\n } catch (err) {\n const message =\n err instanceof Error && err.message\n ? err.message\n : t('customers.config.addressFormat.errorSave', 'Failed to update address settings')\n setError(message)\n flash(message, 'error')\n } finally {\n setPending(null)\n }\n },\n [format, t]\n )\n\n return (\n <section className=\"space-y-4 rounded-lg border bg-background p-4\">\n <header className=\"space-y-1\">\n <h2 className=\"text-lg font-semibold\">\n {t('customers.config.addressFormat.title', 'Customer address format')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'customers.config.addressFormat.description',\n 'Choose how address forms and displays should be structured across the customer module.'\n )}\n </p>\n </header>\n {loading ? (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner className=\"h-4 w-4\" />\n {t('customers.config.addressFormat.loading', 'Loading current preference\u2026')}\n </div>\n ) : (\n <RadioGroup\n className=\"space-y-3\"\n value={format}\n onValueChange={(next) => handleChange(next as AddressFormatStrategy)}\n name=\"address-format\"\n >\n {options.map((option) => {\n const inputId = `address-format-${option.id}`\n return (\n <label key={option.id} htmlFor={inputId} className=\"flex cursor-pointer items-start gap-3 rounded border p-3\">\n <Radio\n id={inputId}\n className=\"mt-1\"\n value={option.id}\n disabled={pending !== null && pending !== option.id}\n />\n <span className=\"space-y-1\">\n <span className=\"block text-sm font-medium\">{option.title}</span>\n <span className=\"block text-xs text-muted-foreground\">{option.description}</span>\n </span>\n </label>\n )\n })}\n {error ? <p className=\"text-sm text-status-error-text\">{error}</p> : null}\n {pending ? (\n <div className=\"inline-flex items-center gap-2 rounded border border-dashed px-3 py-1 text-xs text-muted-foreground\">\n <Spinner className=\"h-3 w-3\" />\n {t('customers.config.addressFormat.updating', 'Saving preference\u2026')}\n </div>\n ) : null}\n </RadioGroup>\n )}\n </section>\n )\n}\n\nexport default AddressFormatSettings\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|