@open-mercato/core 0.7.1-develop.7112.1.d486e6f1b2 → 0.7.1-develop.7120.1.a0154ac412
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/helpers/integration/crmFixtures.js.map +1 -1
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js +9 -2
- package/dist/modules/communication_channels/api/post/channels/[id]/test-send/route.js.map +2 -2
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js +34 -2
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
- package/dist/modules/communication_channels/lib/outbound-recipient.js +5 -1
- package/dist/modules/communication_channels/lib/outbound-recipient.js.map +2 -2
- package/dist/modules/communication_channels/lib/outbound-reply-ref.js +49 -0
- package/dist/modules/communication_channels/lib/outbound-reply-ref.js.map +7 -0
- package/dist/modules/communication_channels/lib/test-seed.js +5 -0
- package/dist/modules/communication_channels/lib/test-seed.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js +1 -1
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.js.map +1 -1
- package/dist/modules/customers/backend/customers/deals/page.js +4 -3
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.js +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.js +1 -1
- package/dist/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.js.map +1 -1
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +1 -1
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/cli.js +5 -5
- package/dist/modules/customers/cli.js.map +2 -2
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/components/detail/pipelineStageUtils.js +1 -0
- package/dist/modules/customers/components/detail/pipelineStageUtils.js.map +2 -2
- package/dist/modules/customers/components/linking/adapters/dealAdapter.js +2 -2
- package/dist/modules/customers/components/linking/adapters/dealAdapter.js.map +2 -2
- package/dist/modules/customers/lib/closureStage.js.map +2 -2
- package/dist/modules/customers/lib/dealStatus.js +6 -4
- package/dist/modules/customers/lib/dealStatus.js.map +2 -2
- package/dist/modules/customers/lib/dealsSummaryQueries.js +3 -3
- package/dist/modules/customers/lib/dealsSummaryQueries.js.map +1 -1
- package/dist/modules/customers/migrations/Migration20260824180000_deal_status_lost.js +30 -0
- package/dist/modules/customers/migrations/Migration20260824180000_deal_status_lost.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/config.js.map +1 -1
- package/dist/modules/eudr/notifications.js +5 -0
- package/dist/modules/eudr/notifications.js.map +2 -2
- package/dist/modules/query_index/lib/search-tokens.js +59 -28
- package/dist/modules/query_index/lib/search-tokens.js.map +2 -2
- package/dist/modules/sales/commands/documents.js +120 -37
- package/dist/modules/sales/commands/documents.js.map +2 -2
- package/dist/modules/warranty_claims/notifications.js +5 -0
- package/dist/modules/warranty_claims/notifications.js.map +2 -2
- package/dist/modules/wms/notifications.js +2 -0
- package/dist/modules/wms/notifications.js.map +2 -2
- package/package.json +7 -7
- package/src/helpers/integration/crmFixtures.ts +1 -1
- package/src/modules/communication_channels/api/post/channels/[id]/test-send/route.ts +21 -9
- package/src/modules/communication_channels/commands/deliver-outbound-message.ts +45 -1
- package/src/modules/communication_channels/lib/adapter.ts +11 -0
- package/src/modules/communication_channels/lib/outbound-recipient.ts +14 -1
- package/src/modules/communication_channels/lib/outbound-reply-ref.ts +138 -0
- package/src/modules/communication_channels/lib/test-seed.ts +19 -0
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealClosure.ts +1 -1
- package/src/modules/customers/backend/customers/deals/page.tsx +4 -3
- package/src/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.tsx +5 -5
- package/src/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.tsx +3 -3
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +2 -2
- package/src/modules/customers/cli.ts +5 -5
- package/src/modules/customers/commands/deals.ts +4 -3
- package/src/modules/customers/components/detail/pipelineStageUtils.ts +1 -0
- package/src/modules/customers/components/linking/adapters/dealAdapter.tsx +2 -2
- package/src/modules/customers/i18n/de.json +1 -1
- package/src/modules/customers/i18n/en.json +1 -1
- package/src/modules/customers/i18n/es.json +1 -1
- package/src/modules/customers/i18n/ko.json +1 -1
- package/src/modules/customers/i18n/pl.json +1 -1
- package/src/modules/customers/lib/closureStage.ts +2 -1
- package/src/modules/customers/lib/dealStatus.ts +23 -12
- package/src/modules/customers/lib/dealsSummaryQueries.ts +3 -3
- package/src/modules/customers/migrations/Migration20260824180000_deal_status_lost.ts +55 -0
- package/src/modules/dashboards/widgets/dashboard/pipeline-summary/config.ts +1 -1
- package/src/modules/eudr/notifications.ts +5 -0
- package/src/modules/query_index/lib/search-tokens.ts +100 -43
- package/src/modules/sales/commands/documents.ts +153 -39
- package/src/modules/warranty_claims/notifications.ts +5 -0
- package/src/modules/wms/notifications.ts +2 -0
package/dist/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../src/modules/customers/backend/customers/deals/pipeline/components/QuickDealDialog.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { ComboboxInput } from '@open-mercato/ui/backend/inputs/ComboboxInput'\nimport { createCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\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 { registerComponent } from '@open-mercato/shared/modules/widgets/component-registry'\n\n// Deal status values written by the kanban flow. These intentionally do NOT include 'closed'\n// because the rest of the app only persists 'loose' for lost deals (see StatusFilterPopover\n// notes). 'in_progress' is kept for backwards compatibility with quick-deals created from\n// the previous version of this dialog. The labels are looked up via i18n at render time so\n// the historic typo 'loose' doesn't leak to operators \u2014 the storage value stays unchanged.\nconst STATUS_OPTIONS = ['open', 'in_progress', 'win', 'loose'] as const\n\n/**\n * Tenant currency option. The page resolves these from the currencies module (see\n * `useCurrencyDictionary`) and passes them in; we no longer hardcode the 4-currency\n * list that previously locked tenants on CZK/SEK/HUF/etc. out of quick-add.\n */\nexport type QuickDealCurrencyOption = {\n code: string\n label?: string\n isBase?: boolean\n}\n\nconst FALLBACK_CURRENCIES: QuickDealCurrencyOption[] = [\n { code: 'USD', isBase: true },\n { code: 'EUR' },\n { code: 'GBP' },\n { code: 'PLN' },\n]\n\nexport type QuickDealContext = {\n pipelineId: string\n pipelineName: string\n pipelineStageId: string\n pipelineStageLabel: string\n}\n\nexport type QuickDealCompanyOption = {\n id: string\n label: string\n}\n\n/**\n * Registered component id for the kanban quick-add dialog. Downstream apps can\n * target it from `widgets/components.ts` to replace/wrap the dialog or to\n * transform its props \u2014 e.g. `defaultProbability: null` so quick-create stops\n * seeding a probability that overrides pipeline automation (#4329).\n */\nexport const QUICK_DEAL_DIALOG_COMPONENT_ID = 'dialog:customers.deals.quickDeal'\n\n/** Probability seeded into the quick-add form when the host passes no override. */\nexport const DEFAULT_QUICK_DEAL_PROBABILITY = 25\n\nexport type QuickDealDialogProps = {\n open: boolean\n context: QuickDealContext | null\n onClose: () => void\n onCreated: () => void\n currentUserId?: string\n currentUserLabel?: string\n companies?: QuickDealCompanyOption[]\n /**\n * Probability pre-filled in the collapsed \"More details\" group. `null` leaves\n * the field empty, so quick-create sends no `probability` at all and whatever\n * the pipeline/automation derives for the target stage stands. Defaults to\n * `DEFAULT_QUICK_DEAL_PROBABILITY` to preserve the historic behavior.\n */\n defaultProbability?: number | null\n /**\n * Tenant currency list. When omitted (e.g. dictionary not yet loaded), we render a\n * conservative fallback so the dialog still works end-to-end. The default selection is\n * always the entry with `isBase: true`, falling back to the first item.\n */\n currencies?: QuickDealCurrencyOption[]\n}\n\ntype QuickDealFormValues = {\n title: string\n valueAmount: number | null\n valueCurrency: string\n companyId: string\n status: (typeof STATUS_OPTIONS)[number]\n probability: number | null\n expectedCloseAt: string\n description: string\n}\n\nconst QUICK_DEAL_CONTEXT_ID = 'customers-deals-kanban:quick-deal'\n\nexport function QuickDealDialog({\n open,\n context,\n onClose,\n onCreated,\n currentUserId,\n currentUserLabel,\n companies = [],\n currencies,\n defaultProbability = DEFAULT_QUICK_DEAL_PROBABILITY,\n}: QuickDealDialogProps): React.ReactElement | null {\n const t = useT()\n // Re-mount CrudForm whenever the dialog opens so cleared state is consistently fresh.\n // We also key on `context?.pipelineStageId` so reopening from a different stage doesn't\n // reuse stale field values (the page passes a fresh stage in `context` each time).\n const [formInstanceKey, setFormInstanceKey] = React.useState(0)\n React.useEffect(() => {\n if (open) setFormInstanceKey((current) => current + 1)\n }, [open])\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: QUICK_DEAL_CONTEXT_ID,\n blockedMessage: translateWithFallback(t, 'ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const titleRequiredMessage = translateWithFallback(\n t,\n 'customers.deals.kanban.quickDeal.title.required',\n 'Title is required.',\n )\n\n // Default currency selection: tenant base currency, else first available, else 'USD' as\n // a last-resort literal that satisfies the form validator (which now accepts any 3-char\n // code rather than a hardcoded enum).\n const resolvedCurrencies = React.useMemo(\n () => (currencies && currencies.length > 0 ? currencies : FALLBACK_CURRENCIES),\n [currencies],\n )\n const defaultCurrencyCode = React.useMemo(() => {\n const base = resolvedCurrencies.find((currency) => currency.isBase)\n return base?.code ?? resolvedCurrencies[0]?.code ?? 'USD'\n }, [resolvedCurrencies])\n\n const formSchema = React.useMemo(\n () =>\n z.object({\n title: z.string().trim().min(1, titleRequiredMessage),\n valueAmount: z.number().min(0).nullable().optional(),\n // The set of valid currencies is tenant-defined and resolved at runtime; we accept\n // any non-empty string here and let the server (which knows the canonical list)\n // reject anything bogus.\n valueCurrency: z.string().min(1),\n companyId: z.string().optional(),\n status: z.enum(STATUS_OPTIONS),\n probability: z.number().min(0).max(100).nullable().optional(),\n expectedCloseAt: z.string().optional(),\n description: z.string().optional(),\n }),\n // Cast widens to QuickDealFormValues so CrudForm's TValues generic stays exact.\n [titleRequiredMessage],\n ) as unknown as z.ZodType<QuickDealFormValues>\n\n const initialValues = React.useMemo<Partial<QuickDealFormValues>>(\n () => ({\n title: '',\n valueAmount: null,\n valueCurrency: defaultCurrencyCode,\n companyId: '',\n status: 'open',\n probability: defaultProbability,\n expectedCloseAt: '',\n description: '',\n }),\n [defaultCurrencyCode, defaultProbability],\n )\n\n const fields = React.useMemo<CrudField[]>(\n () => {\n const list: CrudField[] = [\n {\n // CrudForm auto-focuses the first field of the first group on mount; we don't\n // need (and CrudField doesn't accept) an explicit `autoFocus` prop here.\n id: 'title',\n label: translateWithFallback(t, 'customers.deals.kanban.quickDeal.title.label', 'Deal title'),\n type: 'text',\n required: true,\n placeholder: translateWithFallback(\n t,\n 'customers.deals.kanban.quickDeal.title.placeholder',\n 'e.g. Q3 Expansion \u2014 Lighting Package',\n ),\n },\n {\n id: 'valueAmount',\n label: translateWithFallback(t, 'customers.deals.kanban.quickDeal.value', 'Value'),\n type: 'number',\n layout: 'half',\n placeholder: '0',\n },\n {\n id: 'valueCurrency',\n label: translateWithFallback(t, 'customers.deals.kanban.quickDeal.currency', 'Currency'),\n type: 'select',\n layout: 'half',\n options: resolvedCurrencies.map((currency) => ({\n value: currency.code,\n label: currency.label && currency.label !== currency.code\n ? `${currency.code} \u2014 ${currency.label}`\n : currency.code,\n })),\n },\n {\n // Company is a typeahead (ComboboxInput) rather than a plain <select>: tenants\n // with hundreds of companies get an unusable dropdown otherwise. The `seedOptions`\n // carry the most-recent companies the page already loaded; an async loader could\n // be added later to hit the companies search endpoint for full coverage.\n id: 'companyId',\n label: translateWithFallback(t, 'customers.deals.kanban.quickDeal.company', 'Company'),\n type: 'custom',\n component: ({ value, setValue }) => (\n <ComboboxInput\n value={typeof value === 'string' ? value : ''}\n onChange={(next) => setValue(next)}\n seedOptions={companies.map((company) => ({ value: company.id, label: company.label }))}\n placeholder={translateWithFallback(\n t,\n 'customers.deals.kanban.quickDeal.companyPh',\n 'Pick a company or add new\u2026',\n )}\n allowCustomValues={false}\n />\n ),\n },\n ]\n // Owner is read-only display of the current user (mirrors original UX). Hidden when\n // we don't know the user \u2014 avoids confusing empty avatar cell.\n if (currentUserLabel) {\n list.push({\n id: 'owner',\n label: translateWithFallback(t, 'customers.deals.kanban.quickDeal.owner', 'Owner'),\n type: 'custom',\n readOnly: true,\n component: () => (\n <div className=\"flex h-9 items-center gap-2 rounded-md border border-input bg-card px-3 text-sm\">\n <Avatar label={currentUserLabel} size=\"sm\" />\n <span>{currentUserLabel}</span>\n </div>\n ),\n })\n }\n // Progressive disclosure: status + probability + close date + description live in\n // a separate \"More details\" group with `defaultExpanded: false` (configured via\n // collapsibleGroups on the host CrudForm).\n list.push(\n {\n id: 'status',\n label: translateWithFallback(t, 'customers.deals.kanban.quickDeal.status', 'Status'),\n type: 'select',\n // Storage value is preserved (`loose` etc.) \u2014 only the displayed label is\n // translated, so we never write a localised string back to the DB.\n options: STATUS_OPTIONS.map((statusValue) => ({\n value: statusValue,\n label: translateWithFallback(\n t,\n `customers.deals.kanban.quickDeal.status.${statusValue}`,\n statusValue,\n ),\n })),\n },\n {\n id: 'probability',\n label: translateWithFallback(t, 'customers.deals.kanban.quickDeal.probability', 'Probability'),\n type: 'number',\n layout: 'half',\n placeholder: '25',\n },\n {\n id: 'expectedCloseAt',\n label: translateWithFallback(t, 'customers.deals.kanban.quickDeal.expectedClose', 'Expected close'),\n type: 'date',\n layout: 'half',\n },\n {\n id: 'description',\n label: translateWithFallback(t, 'customers.deals.kanban.quickDeal.description', 'Description'),\n type: 'textarea',\n rows: 3,\n placeholder: translateWithFallback(\n t,\n 'customers.deals.kanban.quickDeal.description.placeholder',\n 'Short context, decision makers or next steps. Markdown supported.',\n ),\n },\n )\n return list\n },\n [companies, currentUserLabel, resolvedCurrencies, t],\n )\n\n const groups = React.useMemo<CrudFormGroup[]>(\n () => [\n {\n id: 'basic',\n // No title \u2014 keep the basic section flush with the dialog header.\n fields: ['title', 'valueAmount', 'valueCurrency', 'companyId', ...(currentUserLabel ? ['owner'] : [])],\n },\n {\n id: 'more',\n title: translateWithFallback(\n t,\n 'customers.deals.kanban.quickDeal.more',\n 'More details (probability, close date, description)',\n ),\n fields: ['status', 'probability', 'expectedCloseAt', 'description'],\n },\n ],\n [currentUserLabel, t],\n )\n\n const handleSubmit = React.useCallback(\n async (values: QuickDealFormValues) => {\n if (!context) return\n const trimmedTitle = values.title.trim()\n if (!trimmedTitle.length) {\n throw createCrudFormError(titleRequiredMessage, { title: titleRequiredMessage })\n }\n const payload: Record<string, unknown> = {\n title: trimmedTitle,\n pipelineId: context.pipelineId,\n pipelineStageId: context.pipelineStageId,\n valueCurrency: values.valueCurrency,\n }\n if (typeof values.valueAmount === 'number' && Number.isFinite(values.valueAmount)) {\n payload.valueAmount = values.valueAmount\n }\n if (currentUserId) payload.ownerUserId = currentUserId\n if (values.companyId) payload.companyIds = [values.companyId]\n if (values.status) payload.status = values.status\n if (typeof values.probability === 'number' && Number.isFinite(values.probability)) {\n payload.probability = values.probability\n }\n if (values.expectedCloseAt && values.expectedCloseAt.trim().length) {\n payload.expectedCloseAt = values.expectedCloseAt\n }\n if (values.description && values.description.trim().length) {\n payload.description = values.description\n }\n const operation = () =>\n createCrud('customers/deals', payload, {\n errorMessage: translateWithFallback(t, 'customers.deals.create.error', 'Failed to create deal.'),\n })\n await runMutation({\n operation,\n context: {\n formId: QUICK_DEAL_CONTEXT_ID,\n resourceKind: 'customers.deal',\n retryLastMutation,\n },\n })\n flash(\n translateWithFallback(\n t,\n 'customers.deals.kanban.quickDeal.success',\n 'Deal added to {stage}.',\n { stage: context.pipelineStageLabel },\n ),\n 'success',\n )\n onCreated()\n onClose()\n },\n [context, currentUserId, onClose, onCreated, retryLastMutation, runMutation, t, titleRequiredMessage],\n )\n\n if (!context) return null\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!next) onClose()\n }}\n >\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {translateWithFallback(t, 'customers.deals.kanban.quickDeal.title', 'Quick deal')}\n </DialogTitle>\n <DialogDescription>\n {translateWithFallback(\n t,\n 'customers.deals.kanban.quickDeal.context',\n 'Pipeline: {pipeline} \u00B7 Stage: {stage}',\n { pipeline: context.pipelineName, stage: context.pipelineStageLabel },\n )}\n </DialogDescription>\n </DialogHeader>\n\n <CrudForm<QuickDealFormValues>\n key={`${context.pipelineStageId}:${formInstanceKey}`}\n embedded\n fields={fields}\n groups={groups}\n // The \"More details\" group starts collapsed; users can expand to set\n // status / probability / close date / description. Mirrors the previous\n // \"+ More details\" toggle.\n collapsibleGroups={{ pageType: 'customers.deals.kanban.quickDeal' }}\n initialValues={initialValues}\n schema={formSchema}\n submitLabel={translateWithFallback(t, 'customers.deals.kanban.quickDeal.submit', 'Add deal')}\n onSubmit={handleSubmit}\n />\n </DialogContent>\n </Dialog>\n )\n}\n\n// Register so downstream apps can reach the dialog from `widgets/components.ts`\n// (replace / wrap / propsTransform) instead of forking the whole kanban page\n// just to change a default (#4329).\nregisterComponent<QuickDealDialogProps>({\n id: QUICK_DEAL_DIALOG_COMPONENT_ID,\n component: QuickDealDialog,\n metadata: {\n module: 'customers',\n description: 'Kanban quick-add deal dialog (pipeline board \"+ deal\").',\n },\n})\n\nexport default QuickDealDialog\n"],
|
|
5
|
-
"mappings": ";AAsOY,cAuBA,YAvBA;AApOZ,YAAY,WAAW;AACvB,SAAS,SAAS;AAClB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,cAAc;AACvB,SAAS,gBAAoD;AAC7D,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB;AAC3B,SAAS,2BAA2B;AACpC,SAAS,0BAA0B;AACnC,SAAS,aAAa;AACtB,SAAS,YAAY;AACrB,SAAS,6BAA6B;AACtC,SAAS,yBAAyB;AAOlC,MAAM,iBAAiB,CAAC,QAAQ,eAAe,OAAO,
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { z } from 'zod'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { ComboboxInput } from '@open-mercato/ui/backend/inputs/ComboboxInput'\nimport { createCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\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 { registerComponent } from '@open-mercato/shared/modules/widgets/component-registry'\n\n// Deal status values written by the kanban flow. These intentionally do NOT include 'closed'\n// because the rest of the app only persists 'lost' for lost deals (see StatusFilterPopover\n// notes). 'in_progress' is kept for backwards compatibility with quick-deals created from\n// the previous version of this dialog. Deals written before 0.7.1 carry the misspelled\n// 'loose'; readers still match it through `lib/dealStatus.ts`, but no writer produces it.\nconst STATUS_OPTIONS = ['open', 'in_progress', 'win', 'lost'] as const\n\n/**\n * Tenant currency option. The page resolves these from the currencies module (see\n * `useCurrencyDictionary`) and passes them in; we no longer hardcode the 4-currency\n * list that previously locked tenants on CZK/SEK/HUF/etc. out of quick-add.\n */\nexport type QuickDealCurrencyOption = {\n code: string\n label?: string\n isBase?: boolean\n}\n\nconst FALLBACK_CURRENCIES: QuickDealCurrencyOption[] = [\n { code: 'USD', isBase: true },\n { code: 'EUR' },\n { code: 'GBP' },\n { code: 'PLN' },\n]\n\nexport type QuickDealContext = {\n pipelineId: string\n pipelineName: string\n pipelineStageId: string\n pipelineStageLabel: string\n}\n\nexport type QuickDealCompanyOption = {\n id: string\n label: string\n}\n\n/**\n * Registered component id for the kanban quick-add dialog. Downstream apps can\n * target it from `widgets/components.ts` to replace/wrap the dialog or to\n * transform its props \u2014 e.g. `defaultProbability: null` so quick-create stops\n * seeding a probability that overrides pipeline automation (#4329).\n */\nexport const QUICK_DEAL_DIALOG_COMPONENT_ID = 'dialog:customers.deals.quickDeal'\n\n/** Probability seeded into the quick-add form when the host passes no override. */\nexport const DEFAULT_QUICK_DEAL_PROBABILITY = 25\n\nexport type QuickDealDialogProps = {\n open: boolean\n context: QuickDealContext | null\n onClose: () => void\n onCreated: () => void\n currentUserId?: string\n currentUserLabel?: string\n companies?: QuickDealCompanyOption[]\n /**\n * Probability pre-filled in the collapsed \"More details\" group. `null` leaves\n * the field empty, so quick-create sends no `probability` at all and whatever\n * the pipeline/automation derives for the target stage stands. Defaults to\n * `DEFAULT_QUICK_DEAL_PROBABILITY` to preserve the historic behavior.\n */\n defaultProbability?: number | null\n /**\n * Tenant currency list. When omitted (e.g. dictionary not yet loaded), we render a\n * conservative fallback so the dialog still works end-to-end. The default selection is\n * always the entry with `isBase: true`, falling back to the first item.\n */\n currencies?: QuickDealCurrencyOption[]\n}\n\ntype QuickDealFormValues = {\n title: string\n valueAmount: number | null\n valueCurrency: string\n companyId: string\n status: (typeof STATUS_OPTIONS)[number]\n probability: number | null\n expectedCloseAt: string\n description: string\n}\n\nconst QUICK_DEAL_CONTEXT_ID = 'customers-deals-kanban:quick-deal'\n\nexport function QuickDealDialog({\n open,\n context,\n onClose,\n onCreated,\n currentUserId,\n currentUserLabel,\n companies = [],\n currencies,\n defaultProbability = DEFAULT_QUICK_DEAL_PROBABILITY,\n}: QuickDealDialogProps): React.ReactElement | null {\n const t = useT()\n // Re-mount CrudForm whenever the dialog opens so cleared state is consistently fresh.\n // We also key on `context?.pipelineStageId` so reopening from a different stage doesn't\n // reuse stale field values (the page passes a fresh stage in `context` each time).\n const [formInstanceKey, setFormInstanceKey] = React.useState(0)\n React.useEffect(() => {\n if (open) setFormInstanceKey((current) => current + 1)\n }, [open])\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: QUICK_DEAL_CONTEXT_ID,\n blockedMessage: translateWithFallback(t, 'ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const titleRequiredMessage = translateWithFallback(\n t,\n 'customers.deals.kanban.quickDeal.title.required',\n 'Title is required.',\n )\n\n // Default currency selection: tenant base currency, else first available, else 'USD' as\n // a last-resort literal that satisfies the form validator (which now accepts any 3-char\n // code rather than a hardcoded enum).\n const resolvedCurrencies = React.useMemo(\n () => (currencies && currencies.length > 0 ? currencies : FALLBACK_CURRENCIES),\n [currencies],\n )\n const defaultCurrencyCode = React.useMemo(() => {\n const base = resolvedCurrencies.find((currency) => currency.isBase)\n return base?.code ?? resolvedCurrencies[0]?.code ?? 'USD'\n }, [resolvedCurrencies])\n\n const formSchema = React.useMemo(\n () =>\n z.object({\n title: z.string().trim().min(1, titleRequiredMessage),\n valueAmount: z.number().min(0).nullable().optional(),\n // The set of valid currencies is tenant-defined and resolved at runtime; we accept\n // any non-empty string here and let the server (which knows the canonical list)\n // reject anything bogus.\n valueCurrency: z.string().min(1),\n companyId: z.string().optional(),\n status: z.enum(STATUS_OPTIONS),\n probability: z.number().min(0).max(100).nullable().optional(),\n expectedCloseAt: z.string().optional(),\n description: z.string().optional(),\n }),\n // Cast widens to QuickDealFormValues so CrudForm's TValues generic stays exact.\n [titleRequiredMessage],\n ) as unknown as z.ZodType<QuickDealFormValues>\n\n const initialValues = React.useMemo<Partial<QuickDealFormValues>>(\n () => ({\n title: '',\n valueAmount: null,\n valueCurrency: defaultCurrencyCode,\n companyId: '',\n status: 'open',\n probability: defaultProbability,\n expectedCloseAt: '',\n description: '',\n }),\n [defaultCurrencyCode, defaultProbability],\n )\n\n const fields = React.useMemo<CrudField[]>(\n () => {\n const list: CrudField[] = [\n {\n // CrudForm auto-focuses the first field of the first group on mount; we don't\n // need (and CrudField doesn't accept) an explicit `autoFocus` prop here.\n id: 'title',\n label: translateWithFallback(t, 'customers.deals.kanban.quickDeal.title.label', 'Deal title'),\n type: 'text',\n required: true,\n placeholder: translateWithFallback(\n t,\n 'customers.deals.kanban.quickDeal.title.placeholder',\n 'e.g. Q3 Expansion \u2014 Lighting Package',\n ),\n },\n {\n id: 'valueAmount',\n label: translateWithFallback(t, 'customers.deals.kanban.quickDeal.value', 'Value'),\n type: 'number',\n layout: 'half',\n placeholder: '0',\n },\n {\n id: 'valueCurrency',\n label: translateWithFallback(t, 'customers.deals.kanban.quickDeal.currency', 'Currency'),\n type: 'select',\n layout: 'half',\n options: resolvedCurrencies.map((currency) => ({\n value: currency.code,\n label: currency.label && currency.label !== currency.code\n ? `${currency.code} \u2014 ${currency.label}`\n : currency.code,\n })),\n },\n {\n // Company is a typeahead (ComboboxInput) rather than a plain <select>: tenants\n // with hundreds of companies get an unusable dropdown otherwise. The `seedOptions`\n // carry the most-recent companies the page already loaded; an async loader could\n // be added later to hit the companies search endpoint for full coverage.\n id: 'companyId',\n label: translateWithFallback(t, 'customers.deals.kanban.quickDeal.company', 'Company'),\n type: 'custom',\n component: ({ value, setValue }) => (\n <ComboboxInput\n value={typeof value === 'string' ? value : ''}\n onChange={(next) => setValue(next)}\n seedOptions={companies.map((company) => ({ value: company.id, label: company.label }))}\n placeholder={translateWithFallback(\n t,\n 'customers.deals.kanban.quickDeal.companyPh',\n 'Pick a company or add new\u2026',\n )}\n allowCustomValues={false}\n />\n ),\n },\n ]\n // Owner is read-only display of the current user (mirrors original UX). Hidden when\n // we don't know the user \u2014 avoids confusing empty avatar cell.\n if (currentUserLabel) {\n list.push({\n id: 'owner',\n label: translateWithFallback(t, 'customers.deals.kanban.quickDeal.owner', 'Owner'),\n type: 'custom',\n readOnly: true,\n component: () => (\n <div className=\"flex h-9 items-center gap-2 rounded-md border border-input bg-card px-3 text-sm\">\n <Avatar label={currentUserLabel} size=\"sm\" />\n <span>{currentUserLabel}</span>\n </div>\n ),\n })\n }\n // Progressive disclosure: status + probability + close date + description live in\n // a separate \"More details\" group with `defaultExpanded: false` (configured via\n // collapsibleGroups on the host CrudForm).\n list.push(\n {\n id: 'status',\n label: translateWithFallback(t, 'customers.deals.kanban.quickDeal.status', 'Status'),\n type: 'select',\n // Storage value is preserved (`lost` etc.), only the displayed label is\n // translated, so we never write a localised string back to the DB.\n options: STATUS_OPTIONS.map((statusValue) => ({\n value: statusValue,\n label: translateWithFallback(\n t,\n `customers.deals.kanban.quickDeal.status.${statusValue}`,\n statusValue,\n ),\n })),\n },\n {\n id: 'probability',\n label: translateWithFallback(t, 'customers.deals.kanban.quickDeal.probability', 'Probability'),\n type: 'number',\n layout: 'half',\n placeholder: '25',\n },\n {\n id: 'expectedCloseAt',\n label: translateWithFallback(t, 'customers.deals.kanban.quickDeal.expectedClose', 'Expected close'),\n type: 'date',\n layout: 'half',\n },\n {\n id: 'description',\n label: translateWithFallback(t, 'customers.deals.kanban.quickDeal.description', 'Description'),\n type: 'textarea',\n rows: 3,\n placeholder: translateWithFallback(\n t,\n 'customers.deals.kanban.quickDeal.description.placeholder',\n 'Short context, decision makers or next steps. Markdown supported.',\n ),\n },\n )\n return list\n },\n [companies, currentUserLabel, resolvedCurrencies, t],\n )\n\n const groups = React.useMemo<CrudFormGroup[]>(\n () => [\n {\n id: 'basic',\n // No title \u2014 keep the basic section flush with the dialog header.\n fields: ['title', 'valueAmount', 'valueCurrency', 'companyId', ...(currentUserLabel ? ['owner'] : [])],\n },\n {\n id: 'more',\n title: translateWithFallback(\n t,\n 'customers.deals.kanban.quickDeal.more',\n 'More details (probability, close date, description)',\n ),\n fields: ['status', 'probability', 'expectedCloseAt', 'description'],\n },\n ],\n [currentUserLabel, t],\n )\n\n const handleSubmit = React.useCallback(\n async (values: QuickDealFormValues) => {\n if (!context) return\n const trimmedTitle = values.title.trim()\n if (!trimmedTitle.length) {\n throw createCrudFormError(titleRequiredMessage, { title: titleRequiredMessage })\n }\n const payload: Record<string, unknown> = {\n title: trimmedTitle,\n pipelineId: context.pipelineId,\n pipelineStageId: context.pipelineStageId,\n valueCurrency: values.valueCurrency,\n }\n if (typeof values.valueAmount === 'number' && Number.isFinite(values.valueAmount)) {\n payload.valueAmount = values.valueAmount\n }\n if (currentUserId) payload.ownerUserId = currentUserId\n if (values.companyId) payload.companyIds = [values.companyId]\n if (values.status) payload.status = values.status\n if (typeof values.probability === 'number' && Number.isFinite(values.probability)) {\n payload.probability = values.probability\n }\n if (values.expectedCloseAt && values.expectedCloseAt.trim().length) {\n payload.expectedCloseAt = values.expectedCloseAt\n }\n if (values.description && values.description.trim().length) {\n payload.description = values.description\n }\n const operation = () =>\n createCrud('customers/deals', payload, {\n errorMessage: translateWithFallback(t, 'customers.deals.create.error', 'Failed to create deal.'),\n })\n await runMutation({\n operation,\n context: {\n formId: QUICK_DEAL_CONTEXT_ID,\n resourceKind: 'customers.deal',\n retryLastMutation,\n },\n })\n flash(\n translateWithFallback(\n t,\n 'customers.deals.kanban.quickDeal.success',\n 'Deal added to {stage}.',\n { stage: context.pipelineStageLabel },\n ),\n 'success',\n )\n onCreated()\n onClose()\n },\n [context, currentUserId, onClose, onCreated, retryLastMutation, runMutation, t, titleRequiredMessage],\n )\n\n if (!context) return null\n\n return (\n <Dialog\n open={open}\n onOpenChange={(next) => {\n if (!next) onClose()\n }}\n >\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {translateWithFallback(t, 'customers.deals.kanban.quickDeal.title', 'Quick deal')}\n </DialogTitle>\n <DialogDescription>\n {translateWithFallback(\n t,\n 'customers.deals.kanban.quickDeal.context',\n 'Pipeline: {pipeline} \u00B7 Stage: {stage}',\n { pipeline: context.pipelineName, stage: context.pipelineStageLabel },\n )}\n </DialogDescription>\n </DialogHeader>\n\n <CrudForm<QuickDealFormValues>\n key={`${context.pipelineStageId}:${formInstanceKey}`}\n embedded\n fields={fields}\n groups={groups}\n // The \"More details\" group starts collapsed; users can expand to set\n // status / probability / close date / description. Mirrors the previous\n // \"+ More details\" toggle.\n collapsibleGroups={{ pageType: 'customers.deals.kanban.quickDeal' }}\n initialValues={initialValues}\n schema={formSchema}\n submitLabel={translateWithFallback(t, 'customers.deals.kanban.quickDeal.submit', 'Add deal')}\n onSubmit={handleSubmit}\n />\n </DialogContent>\n </Dialog>\n )\n}\n\n// Register so downstream apps can reach the dialog from `widgets/components.ts`\n// (replace / wrap / propsTransform) instead of forking the whole kanban page\n// just to change a default (#4329).\nregisterComponent<QuickDealDialogProps>({\n id: QUICK_DEAL_DIALOG_COMPONENT_ID,\n component: QuickDealDialog,\n metadata: {\n module: 'customers',\n description: 'Kanban quick-add deal dialog (pipeline board \"+ deal\").',\n },\n})\n\nexport default QuickDealDialog\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../src/modules/customers/backend/customers/deals/pipeline/components/StatusFilterPopover.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { X } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '@open-mercato/ui/primitives/popover'\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'\nimport { mapDictionaryColorToTone } from '@open-mercato/shared/lib/query/advanced-filter'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { useCustomerDictionary } from '../../../../../components/detail/hooks/useCustomerDictionary'\nimport { canonicalDealStatus } from '../../../../../lib/dealStatus'\nimport { toneToDotClass } from './toneClasses'\nimport { ChipButton } from './ChipButton'\nimport { FilterPopoverShell } from './FilterPopoverShell'\n\n/**\n * Filter options exposed to the operator.\n *\n * The deal `status` column is dictionary-driven (`deal-statuses`); the seeded dictionary\n * carries five values (`open`, `closed`, `win`, `
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { X } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '@open-mercato/ui/primitives/popover'\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'\nimport { mapDictionaryColorToTone } from '@open-mercato/shared/lib/query/advanced-filter'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { useCustomerDictionary } from '../../../../../components/detail/hooks/useCustomerDictionary'\nimport { canonicalDealStatus } from '../../../../../lib/dealStatus'\nimport { toneToDotClass } from './toneClasses'\nimport { ChipButton } from './ChipButton'\nimport { FilterPopoverShell } from './FilterPopoverShell'\n\n/**\n * Filter options exposed to the operator.\n *\n * The deal `status` column is dictionary-driven (`deal-statuses`); the seeded dictionary\n * carries five values (`open`, `closed`, `win`, `lost`, `in_progress`) plus any\n * tenant-custom entries. We render every dictionary entry de-duplicated by canonical\n * spelling so the kanban Status pill stays aligned with the list page's advanced filter\n * (which also uses the dictionary). A hard-coded fallback keeps the popover usable while\n * the dictionary is loading or when a tenant has no entries.\n *\n * `won` / `loose` are accepted as aliases for `win` / `lost` at the API layer (see\n * `lib/dealStatus.ts:expandDealStatusAliases`, shared by the deals list route and the\n * kanban aggregate route). The UI only exposes the canonical values to avoid duplicate\n * pills, but `canonicalDealStatus` normalizes any alias passed in through `values` so\n * the chip and draft selection render the correct label.\n */\nconst FALLBACK_STATUS_OPTIONS: Array<{\n value: string\n labelKey: string\n labelFallback: string\n dotClass: string\n}> = [\n {\n value: 'open',\n labelKey: 'customers.deals.kanban.filter.status.open',\n labelFallback: 'Open',\n // Tones mirror mapDictionaryColorToTone over the seeded dictionary colors\n // (#2563eb \u2192 info, #22c55e \u2192 success, #ef4444 \u2192 error) so the fallback pills\n // render identically to the dictionary-driven ones.\n dotClass: 'bg-status-info-icon',\n },\n {\n value: 'win',\n labelKey: 'customers.deals.kanban.filter.status.won',\n labelFallback: 'Won',\n dotClass: 'bg-status-success-icon',\n },\n {\n value: 'lost',\n labelKey: 'customers.deals.kanban.filter.status.lost',\n labelFallback: 'Lost',\n dotClass: 'bg-status-error-icon',\n },\n {\n value: 'closed',\n labelKey: 'customers.deals.kanban.filter.status.closed',\n labelFallback: 'Closed',\n dotClass: 'bg-status-neutral-icon',\n },\n {\n value: 'in_progress',\n labelKey: 'customers.deals.kanban.filter.status.inProgress',\n labelFallback: 'In progress',\n dotClass: 'bg-status-warning-icon',\n },\n]\n\ntype StatusFilterPopoverProps = {\n values: string[]\n onApply: (next: string[]) => void\n}\n\nexport function StatusFilterPopover({ values, onApply }: StatusFilterPopoverProps): React.ReactElement {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const { data: dictionaryData, isLoading: dictionaryLoading } = useCustomerDictionary(\n 'deal-statuses',\n scopeVersion,\n )\n const [open, setOpen] = React.useState(false)\n const normalizedValues = React.useMemo(\n () => Array.from(new Set(values.map(canonicalDealStatus))),\n [values],\n )\n const [draft, setDraft] = React.useState<string[]>(normalizedValues)\n\n React.useEffect(() => {\n if (open) setDraft(normalizedValues)\n }, [open, normalizedValues])\n\n const statusOptions = React.useMemo(() => {\n const entries = dictionaryData?.entries\n if (entries && entries.length > 0) {\n const byCanonical = new Map<string, { value: string; label: string; dotClass: string }>()\n for (const entry of entries) {\n const canonical = canonicalDealStatus(entry.value)\n if (byCanonical.has(canonical)) continue\n const tone = mapDictionaryColorToTone(entry.color ?? null)\n byCanonical.set(canonical, {\n value: canonical,\n label: entry.label,\n dotClass: toneToDotClass(tone),\n })\n }\n // Sort by label exactly like the List page's advanced filter\n // (backend/customers/deals/page.tsx dictionaryOptions) so both surfaces render\n // the same pills in the same order.\n return Array.from(byCanonical.values()).sort((a, b) =>\n a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }),\n )\n }\n return FALLBACK_STATUS_OPTIONS.map((entry) => ({\n value: entry.value,\n label: translateWithFallback(t, entry.labelKey, entry.labelFallback),\n dotClass: entry.dotClass,\n }))\n }, [dictionaryData, t])\n\n const labelByValue = React.useMemo(() => {\n const map = new Map<string, string>()\n for (const option of statusOptions) {\n map.set(option.value, option.label)\n }\n for (const fallback of FALLBACK_STATUS_OPTIONS) {\n if (!map.has(fallback.value)) {\n map.set(fallback.value, translateWithFallback(t, fallback.labelKey, fallback.labelFallback))\n }\n }\n return map\n }, [statusOptions, t])\n\n const chipLabel = translateWithFallback(t, 'customers.deals.kanban.filter.status', 'Status')\n const chipValue =\n normalizedValues.length === 0\n ? translateWithFallback(t, 'customers.deals.kanban.filter.all', 'All')\n : normalizedValues\n .map((value) => labelByValue.get(value) ?? value)\n .join(', ')\n\n const toggleDraft = (value: string) => {\n const normalized = canonicalDealStatus(value)\n setDraft((prev) =>\n prev.includes(normalized) ? prev.filter((entry) => entry !== normalized) : [...prev, normalized],\n )\n }\n\n const handleApply = () => {\n onApply(draft)\n setOpen(false)\n }\n\n // Cmd/Ctrl+Enter from anywhere inside the popover confirms \u2014 parity with the dialog\n // primary-action shortcut (`AGENTS.md` UI Interaction rules).\n const handleKeyDown = (event: React.KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n handleApply()\n }\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <ChipButton label={chipLabel} value={chipValue} active={normalizedValues.length > 0} />\n </PopoverTrigger>\n <PopoverContent\n className=\"w-96 rounded-2xl border-border bg-transparent p-0 shadow-xl\"\n align=\"start\"\n onKeyDown={handleKeyDown}\n >\n <FilterPopoverShell\n title={\n <>\n <span className=\"font-bold\">\n {translateWithFallback(t, 'customers.deals.kanban.filter.status.title.label', 'Filter : ')}\n </span>\n <span className=\"font-normal\">\n {translateWithFallback(t, 'customers.deals.kanban.filter.status', 'Status')}\n </span>\n </>\n }\n onClose={() => setOpen(false)}\n onCancel={() => setOpen(false)}\n onApply={handleApply}\n footerLeft={\n <span>\n {draft.length}{' '}\n {translateWithFallback(t, 'customers.deals.kanban.filter.selected', 'selected')}\n </span>\n }\n >\n <span className=\"text-xs font-semibold uppercase leading-normal tracking-wide text-muted-foreground\">\n {translateWithFallback(t, 'customers.deals.kanban.filter.status', 'Status')}\n </span>\n <div className=\"flex flex-wrap items-center gap-x-1.5 gap-y-2\">\n {dictionaryLoading ? (\n <span className=\"flex items-center gap-2 text-xs text-muted-foreground\" role=\"status\" aria-live=\"polite\">\n <Spinner className=\"size-3\" />\n {translateWithFallback(\n t,\n 'customers.deals.kanban.filter.status.loading',\n 'Loading statuses\u2026',\n )}\n </span>\n ) : (\n statusOptions.map((option) => {\n const isSelected = draft.includes(option.value)\n const label = option.label\n return (\n <Button\n key={option.value}\n type=\"button\"\n variant=\"ghost\"\n size=\"2xs\"\n onClick={() => toggleDraft(option.value)}\n aria-pressed={isSelected}\n className={`gap-1.5 rounded-full px-2.5 py-1.5 text-xs leading-normal ${\n isSelected\n ? 'bg-muted font-semibold text-foreground'\n : 'border border-border bg-card font-normal text-muted-foreground hover:bg-muted'\n }`}\n >\n <span\n className={`inline-block size-2 shrink-0 rounded-full ${option.dotClass}`}\n aria-hidden=\"true\"\n />\n <span>{label}</span>\n {isSelected ? (\n <X className=\"size-2.5 shrink-0 text-muted-foreground\" aria-hidden=\"true\" />\n ) : null}\n </Button>\n )\n })\n )}\n </div>\n </FilterPopoverShell>\n </PopoverContent>\n </Popover>\n )\n}\n\nexport default StatusFilterPopover\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1890,7 +1890,7 @@ function DealsKanbanPage() {
|
|
|
1890
1890
|
{
|
|
1891
1891
|
id: "mark-lost",
|
|
1892
1892
|
label: translateWithFallback(t, "customers.deals.kanban.menu.markLost", "Mark as Lost"),
|
|
1893
|
-
onSelect: () => void updateDealStatus(deal.id, "
|
|
1893
|
+
onSelect: () => void updateDealStatus(deal.id, "lost")
|
|
1894
1894
|
},
|
|
1895
1895
|
{
|
|
1896
1896
|
id: "delete",
|