@open-mercato/core 0.6.4-develop.3929.1.fcf7afece2 → 0.6.4-develop.3944.1.4100aa7fbe
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/customers/backend/customers/deals/create/page.js +3 -61
- package/dist/modules/customers/backend/customers/deals/create/page.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +2 -0
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/create/CreateDealForm.js +233 -0
- package/dist/modules/customers/components/detail/create/CreateDealForm.js.map +7 -0
- package/dist/modules/customers/components/detail/create/DealAssociationsField.js +209 -0
- package/dist/modules/customers/components/detail/create/DealAssociationsField.js.map +7 -0
- package/dist/modules/customers/components/detail/create/DealAssociationsSection.js +67 -0
- package/dist/modules/customers/components/detail/create/DealAssociationsSection.js.map +7 -0
- package/dist/modules/customers/components/detail/create/DealCreateSidebar.js +73 -0
- package/dist/modules/customers/components/detail/create/DealCreateSidebar.js.map +7 -0
- package/dist/modules/customers/components/detail/create/DealCurrencyField.js +92 -0
- package/dist/modules/customers/components/detail/create/DealCurrencyField.js.map +7 -0
- package/dist/modules/customers/components/detail/create/DealCustomAttributes.js +81 -0
- package/dist/modules/customers/components/detail/create/DealCustomAttributes.js.map +7 -0
- package/dist/modules/customers/components/detail/create/DealDetailsFields.js +171 -0
- package/dist/modules/customers/components/detail/create/DealDetailsFields.js.map +7 -0
- package/dist/modules/customers/components/detail/create/DealFormField.js +24 -0
- package/dist/modules/customers/components/detail/create/DealFormField.js.map +7 -0
- package/dist/modules/customers/components/detail/create/DealSectionCard.js +29 -0
- package/dist/modules/customers/components/detail/create/DealSectionCard.js.map +7 -0
- package/dist/modules/customers/components/detail/create/DealTipsCard.js +19 -0
- package/dist/modules/customers/components/detail/create/DealTipsCard.js.map +7 -0
- package/dist/modules/customers/components/detail/create/PipelineSelect.js +41 -0
- package/dist/modules/customers/components/detail/create/PipelineSelect.js.map +7 -0
- package/dist/modules/customers/components/detail/create/PipelineStageSelect.js +49 -0
- package/dist/modules/customers/components/detail/create/PipelineStageSelect.js.map +7 -0
- package/dist/modules/customers/components/detail/create/SuffixInput.js +21 -0
- package/dist/modules/customers/components/detail/create/SuffixInput.js.map +7 -0
- package/dist/modules/customers/components/detail/create/dealCustomFieldControl.js +270 -0
- package/dist/modules/customers/components/detail/create/dealCustomFieldControl.js.map +7 -0
- package/dist/modules/customers/components/detail/create/dealFormTypes.js +17 -0
- package/dist/modules/customers/components/detail/create/dealFormTypes.js.map +7 -0
- package/dist/modules/customers/components/detail/create/dealNumericInput.js +16 -0
- package/dist/modules/customers/components/detail/create/dealNumericInput.js.map +7 -0
- package/dist/modules/customers/components/detail/create/useDealCustomFields.js +93 -0
- package/dist/modules/customers/components/detail/create/useDealCustomFields.js.map +7 -0
- package/dist/modules/customers/components/detail/create/useDealPipelines.js +59 -0
- package/dist/modules/customers/components/detail/create/useDealPipelines.js.map +7 -0
- package/dist/modules/customers/components/formConfig.js +4 -2
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/dictionaries/components/DictionaryEntrySelect.js +5 -2
- package/dist/modules/dictionaries/components/DictionaryEntrySelect.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customers/backend/customers/deals/create/page.tsx +3 -64
- package/src/modules/customers/components/detail/DealForm.tsx +2 -0
- package/src/modules/customers/components/detail/create/CreateDealForm.tsx +254 -0
- package/src/modules/customers/components/detail/create/DealAssociationsField.tsx +253 -0
- package/src/modules/customers/components/detail/create/DealAssociationsSection.tsx +72 -0
- package/src/modules/customers/components/detail/create/DealCreateSidebar.tsx +79 -0
- package/src/modules/customers/components/detail/create/DealCurrencyField.tsx +108 -0
- package/src/modules/customers/components/detail/create/DealCustomAttributes.tsx +118 -0
- package/src/modules/customers/components/detail/create/DealDetailsFields.tsx +171 -0
- package/src/modules/customers/components/detail/create/DealFormField.tsx +39 -0
- package/src/modules/customers/components/detail/create/DealSectionCard.tsx +40 -0
- package/src/modules/customers/components/detail/create/DealTipsCard.tsx +26 -0
- package/src/modules/customers/components/detail/create/PipelineSelect.tsx +55 -0
- package/src/modules/customers/components/detail/create/PipelineStageSelect.tsx +70 -0
- package/src/modules/customers/components/detail/create/SuffixInput.tsx +20 -0
- package/src/modules/customers/components/detail/create/dealCustomFieldControl.tsx +310 -0
- package/src/modules/customers/components/detail/create/dealFormTypes.ts +29 -0
- package/src/modules/customers/components/detail/create/dealNumericInput.ts +20 -0
- package/src/modules/customers/components/detail/create/useDealCustomFields.ts +118 -0
- package/src/modules/customers/components/detail/create/useDealPipelines.ts +80 -0
- package/src/modules/customers/components/formConfig.tsx +3 -0
- package/src/modules/customers/i18n/de.json +26 -0
- package/src/modules/customers/i18n/en.json +26 -0
- package/src/modules/customers/i18n/es.json +26 -0
- package/src/modules/customers/i18n/pl.json +26 -0
- package/src/modules/dictionaries/components/DictionaryEntrySelect.tsx +12 -1
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { Briefcase, Save } from "lucide-react";
|
|
6
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
7
|
+
import { translateWithFallback } from "@open-mercato/shared/lib/i18n/translate";
|
|
8
|
+
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
9
|
+
import { createCrud } from "@open-mercato/ui/backend/utils/crud";
|
|
10
|
+
import { useGuardedMutation } from "@open-mercato/ui/backend/injection/useGuardedMutation";
|
|
11
|
+
import { FormHeader } from "@open-mercato/ui/backend/forms";
|
|
12
|
+
import { Button } from "@open-mercato/ui/primitives/button";
|
|
13
|
+
import { Spinner } from "@open-mercato/ui/primitives/spinner";
|
|
14
|
+
import { dealFormSchema } from "../DealForm.js";
|
|
15
|
+
import { createDictionarySelectLabels } from "../utils.js";
|
|
16
|
+
import { DealSectionCard } from "./DealSectionCard.js";
|
|
17
|
+
import { DealDetailsFields } from "./DealDetailsFields.js";
|
|
18
|
+
import { DealAssociationsSection } from "./DealAssociationsSection.js";
|
|
19
|
+
import { DealCreateSidebar } from "./DealCreateSidebar.js";
|
|
20
|
+
import { useDealPipelines } from "./useDealPipelines.js";
|
|
21
|
+
import { useDealCustomFields } from "./useDealCustomFields.js";
|
|
22
|
+
import { EMPTY_VALUES } from "./dealFormTypes.js";
|
|
23
|
+
const CONTEXT_ID = "customers.deals.create";
|
|
24
|
+
const DEAL_ENTITY_ID = "customers:customer_deal";
|
|
25
|
+
const CUSTOM_FIELDS_MANAGE_HREF = `/backend/entities/system/${encodeURIComponent(DEAL_ENTITY_ID)}`;
|
|
26
|
+
function CreateDealForm({ returnTo }) {
|
|
27
|
+
const t = useT();
|
|
28
|
+
const router = useRouter();
|
|
29
|
+
const tr = React.useCallback(
|
|
30
|
+
(key, fallback, params) => translateWithFallback(t, key, fallback, params),
|
|
31
|
+
[t]
|
|
32
|
+
);
|
|
33
|
+
const [values, setValues] = React.useState(EMPTY_VALUES);
|
|
34
|
+
const [errors, setErrors] = React.useState({});
|
|
35
|
+
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
36
|
+
const { pipelines, stages, loadStages } = useDealPipelines();
|
|
37
|
+
const {
|
|
38
|
+
customValues,
|
|
39
|
+
customFieldsLoaded,
|
|
40
|
+
customCount,
|
|
41
|
+
handleCustomChange,
|
|
42
|
+
handleCustomAttributesLoaded,
|
|
43
|
+
validateCustomFields,
|
|
44
|
+
collectNormalizedCustomValues
|
|
45
|
+
} = useDealCustomFields(tr);
|
|
46
|
+
const { runMutation, retryLastMutation } = useGuardedMutation({
|
|
47
|
+
contextId: CONTEXT_ID,
|
|
48
|
+
blockedMessage: tr("ui.forms.flash.saveBlocked", "Save blocked by validation")
|
|
49
|
+
});
|
|
50
|
+
const statusLabels = React.useMemo(
|
|
51
|
+
() => createDictionarySelectLabels("deal-statuses", (key, fallback) => tr(key, fallback ?? key)),
|
|
52
|
+
[tr]
|
|
53
|
+
);
|
|
54
|
+
const patch = React.useCallback((partial) => {
|
|
55
|
+
setValues((current) => ({ ...current, ...partial }));
|
|
56
|
+
}, []);
|
|
57
|
+
const handlePipelineChange = React.useCallback(
|
|
58
|
+
(id) => {
|
|
59
|
+
patch({ pipelineId: id, pipelineStageId: "" });
|
|
60
|
+
loadStages(id).catch(() => {
|
|
61
|
+
});
|
|
62
|
+
},
|
|
63
|
+
[loadStages, patch]
|
|
64
|
+
);
|
|
65
|
+
const handleCancel = React.useCallback(() => {
|
|
66
|
+
router.push(returnTo);
|
|
67
|
+
}, [returnTo, router]);
|
|
68
|
+
const handleSubmit = React.useCallback(async () => {
|
|
69
|
+
if (isSubmitting) return;
|
|
70
|
+
if (!customFieldsLoaded) {
|
|
71
|
+
flash(tr("customers.deals.create.sections.custom.loading", "Loading custom fields..."), "error");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const merged = { ...values, ...customValues };
|
|
75
|
+
const parsed = dealFormSchema.safeParse(merged);
|
|
76
|
+
if (!parsed.success) {
|
|
77
|
+
const fieldErrors = {};
|
|
78
|
+
for (const issue of parsed.error.issues) {
|
|
79
|
+
const key = typeof issue.path[0] === "string" ? issue.path[0] : void 0;
|
|
80
|
+
if (key && !fieldErrors[key]) fieldErrors[key] = tr(issue.message, issue.message);
|
|
81
|
+
}
|
|
82
|
+
setErrors(fieldErrors);
|
|
83
|
+
const firstMessage = Object.values(fieldErrors)[0];
|
|
84
|
+
if (firstMessage) flash(firstMessage, "error");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const customFieldErrors = validateCustomFields(merged);
|
|
88
|
+
if (Object.keys(customFieldErrors).length) {
|
|
89
|
+
setErrors(customFieldErrors);
|
|
90
|
+
const firstMessage = Object.values(customFieldErrors)[0];
|
|
91
|
+
if (firstMessage) flash(firstMessage, "error");
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
setErrors({});
|
|
95
|
+
setIsSubmitting(true);
|
|
96
|
+
try {
|
|
97
|
+
const data = parsed.data;
|
|
98
|
+
const expectedCloseAt = data.expectedCloseAt && data.expectedCloseAt.length ? new Date(data.expectedCloseAt).toISOString() : void 0;
|
|
99
|
+
const payload = {
|
|
100
|
+
title: data.title,
|
|
101
|
+
status: data.status || void 0,
|
|
102
|
+
pipelineId: data.pipelineId || void 0,
|
|
103
|
+
pipelineStageId: data.pipelineStageId || void 0,
|
|
104
|
+
valueAmount: typeof data.valueAmount === "number" ? data.valueAmount : void 0,
|
|
105
|
+
valueCurrency: data.valueCurrency || void 0,
|
|
106
|
+
probability: typeof data.probability === "number" ? data.probability : void 0,
|
|
107
|
+
expectedCloseAt,
|
|
108
|
+
description: data.description && data.description.length ? data.description : void 0,
|
|
109
|
+
personIds: values.personIds.length ? values.personIds : void 0,
|
|
110
|
+
companyIds: values.companyIds.length ? values.companyIds : void 0
|
|
111
|
+
};
|
|
112
|
+
const custom = collectNormalizedCustomValues(merged);
|
|
113
|
+
if (Object.keys(custom).length) payload.customFields = custom;
|
|
114
|
+
await runMutation({
|
|
115
|
+
operation: () => createCrud("customers/deals", payload, {
|
|
116
|
+
errorMessage: tr("customers.deals.create.error", "Failed to create deal.")
|
|
117
|
+
}),
|
|
118
|
+
context: { formId: CONTEXT_ID, resourceKind: "customers.deal", retryLastMutation },
|
|
119
|
+
mutationPayload: payload
|
|
120
|
+
});
|
|
121
|
+
flash(tr("customers.people.detail.deals.success", "Deal created."), "success");
|
|
122
|
+
router.push(returnTo);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
const message = err instanceof Error ? err.message : tr("customers.deals.create.error", "Failed to create deal.");
|
|
125
|
+
flash(message, "error");
|
|
126
|
+
} finally {
|
|
127
|
+
setIsSubmitting(false);
|
|
128
|
+
}
|
|
129
|
+
}, [
|
|
130
|
+
collectNormalizedCustomValues,
|
|
131
|
+
customFieldsLoaded,
|
|
132
|
+
customValues,
|
|
133
|
+
isSubmitting,
|
|
134
|
+
retryLastMutation,
|
|
135
|
+
returnTo,
|
|
136
|
+
router,
|
|
137
|
+
runMutation,
|
|
138
|
+
tr,
|
|
139
|
+
validateCustomFields,
|
|
140
|
+
values
|
|
141
|
+
]);
|
|
142
|
+
const onKeyDown = React.useCallback(
|
|
143
|
+
(event) => {
|
|
144
|
+
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
|
|
145
|
+
event.preventDefault();
|
|
146
|
+
handleSubmit();
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
[handleSubmit]
|
|
150
|
+
);
|
|
151
|
+
const onFormSubmit = React.useCallback(
|
|
152
|
+
(event) => {
|
|
153
|
+
event.preventDefault();
|
|
154
|
+
handleSubmit();
|
|
155
|
+
},
|
|
156
|
+
[handleSubmit]
|
|
157
|
+
);
|
|
158
|
+
const cancelLabel = tr("customers.deals.create.cancel", "Cancel");
|
|
159
|
+
const submitLabel = tr("customers.deals.create.submit", "Create deal");
|
|
160
|
+
const submitDisabled = !customFieldsLoaded;
|
|
161
|
+
return /* @__PURE__ */ jsxs("form", { className: "mx-auto max-w-screen-2xl", onKeyDown, onSubmit: onFormSubmit, noValidate: true, children: [
|
|
162
|
+
/* @__PURE__ */ jsx(
|
|
163
|
+
FormHeader,
|
|
164
|
+
{
|
|
165
|
+
backHref: returnTo,
|
|
166
|
+
backLabel: tr("customers.deals.create.back", "Back to deals")
|
|
167
|
+
}
|
|
168
|
+
),
|
|
169
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-6 grid grid-cols-1 gap-5 lg:grid-cols-[minmax(0,1fr)_330px]", children: [
|
|
170
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
171
|
+
/* @__PURE__ */ jsx(
|
|
172
|
+
DealSectionCard,
|
|
173
|
+
{
|
|
174
|
+
icon: Briefcase,
|
|
175
|
+
title: tr("customers.deals.create.title", "Create deal"),
|
|
176
|
+
subtitle: tr("customers.deals.create.sections.details.subtitle", "Core opportunity info"),
|
|
177
|
+
actions: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
178
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: handleCancel, disabled: isSubmitting, children: cancelLabel }),
|
|
179
|
+
/* @__PURE__ */ jsxs(Button, { type: "button", onClick: handleSubmit, disabled: isSubmitting || submitDisabled, children: [
|
|
180
|
+
isSubmitting ? /* @__PURE__ */ jsx(Spinner, { className: "size-4" }) : /* @__PURE__ */ jsx(Save, { className: "size-4" }),
|
|
181
|
+
submitLabel
|
|
182
|
+
] })
|
|
183
|
+
] }),
|
|
184
|
+
children: /* @__PURE__ */ jsx(
|
|
185
|
+
DealDetailsFields,
|
|
186
|
+
{
|
|
187
|
+
values,
|
|
188
|
+
errors,
|
|
189
|
+
isSubmitting,
|
|
190
|
+
patch,
|
|
191
|
+
onPipelineChange: handlePipelineChange,
|
|
192
|
+
pipelines,
|
|
193
|
+
stages,
|
|
194
|
+
statusLabels,
|
|
195
|
+
tr
|
|
196
|
+
}
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
),
|
|
200
|
+
/* @__PURE__ */ jsx(
|
|
201
|
+
DealAssociationsSection,
|
|
202
|
+
{
|
|
203
|
+
tr,
|
|
204
|
+
personIds: values.personIds,
|
|
205
|
+
companyIds: values.companyIds,
|
|
206
|
+
onPeopleChange: (next) => patch({ personIds: next }),
|
|
207
|
+
onCompaniesChange: (next) => patch({ companyIds: next }),
|
|
208
|
+
disabled: isSubmitting
|
|
209
|
+
}
|
|
210
|
+
)
|
|
211
|
+
] }),
|
|
212
|
+
/* @__PURE__ */ jsx(
|
|
213
|
+
DealCreateSidebar,
|
|
214
|
+
{
|
|
215
|
+
tr,
|
|
216
|
+
customValues,
|
|
217
|
+
onCustomChange: handleCustomChange,
|
|
218
|
+
errors,
|
|
219
|
+
disabled: isSubmitting,
|
|
220
|
+
customCount,
|
|
221
|
+
manageHref: CUSTOM_FIELDS_MANAGE_HREF,
|
|
222
|
+
onCustomLoaded: handleCustomAttributesLoaded
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
] })
|
|
226
|
+
] });
|
|
227
|
+
}
|
|
228
|
+
var CreateDealForm_default = CreateDealForm;
|
|
229
|
+
export {
|
|
230
|
+
CreateDealForm,
|
|
231
|
+
CreateDealForm_default as default
|
|
232
|
+
};
|
|
233
|
+
//# sourceMappingURL=CreateDealForm.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/customers/components/detail/create/CreateDealForm.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Briefcase, Save } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { translateWithFallback } from '@open-mercato/shared/lib/i18n/translate'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { createCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { FormHeader } from '@open-mercato/ui/backend/forms'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { dealFormSchema } from '../DealForm'\nimport { createDictionarySelectLabels } from '../utils'\nimport { DealSectionCard } from './DealSectionCard'\nimport { DealDetailsFields } from './DealDetailsFields'\nimport { DealAssociationsSection } from './DealAssociationsSection'\nimport { DealCreateSidebar } from './DealCreateSidebar'\nimport { useDealPipelines } from './useDealPipelines'\nimport { useDealCustomFields } from './useDealCustomFields'\nimport { EMPTY_VALUES, type BaseValues } from './dealFormTypes'\n\nconst CONTEXT_ID = 'customers.deals.create'\nconst DEAL_ENTITY_ID = 'customers:customer_deal'\nconst CUSTOM_FIELDS_MANAGE_HREF = `/backend/entities/system/${encodeURIComponent(DEAL_ENTITY_ID)}`\n\nexport type CreateDealFormProps = {\n returnTo: string\n}\n\nexport function CreateDealForm({ returnTo }: CreateDealFormProps) {\n const t = useT()\n const router = useRouter()\n const tr = React.useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>) =>\n translateWithFallback(t, key, fallback, params),\n [t],\n )\n\n const [values, setValues] = React.useState<BaseValues>(EMPTY_VALUES)\n const [errors, setErrors] = React.useState<Record<string, string>>({})\n const [isSubmitting, setIsSubmitting] = React.useState(false)\n\n const { pipelines, stages, loadStages } = useDealPipelines()\n const {\n customValues,\n customFieldsLoaded,\n customCount,\n handleCustomChange,\n handleCustomAttributesLoaded,\n validateCustomFields,\n collectNormalizedCustomValues,\n } = useDealCustomFields(tr)\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: CONTEXT_ID,\n blockedMessage: tr('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const statusLabels = React.useMemo(\n () => createDictionarySelectLabels('deal-statuses', (key, fallback) => tr(key, fallback ?? key)),\n [tr],\n )\n\n const patch = React.useCallback((partial: Partial<BaseValues>) => {\n setValues((current) => ({ ...current, ...partial }))\n }, [])\n\n const handlePipelineChange = React.useCallback(\n (id: string) => {\n patch({ pipelineId: id, pipelineStageId: '' })\n // loadStages resets stages to [] on failure; the rejection is intentionally ignored here.\n loadStages(id).catch(() => {})\n },\n [loadStages, patch],\n )\n\n const handleCancel = React.useCallback(() => {\n router.push(returnTo)\n }, [returnTo, router])\n\n const handleSubmit = React.useCallback(async () => {\n if (isSubmitting) return\n if (!customFieldsLoaded) {\n flash(tr('customers.deals.create.sections.custom.loading', 'Loading custom fields...'), 'error')\n return\n }\n const merged = { ...values, ...customValues }\n const parsed = dealFormSchema.safeParse(merged)\n if (!parsed.success) {\n const fieldErrors: Record<string, string> = {}\n for (const issue of parsed.error.issues) {\n const key = typeof issue.path[0] === 'string' ? issue.path[0] : undefined\n if (key && !fieldErrors[key]) fieldErrors[key] = tr(issue.message, issue.message)\n }\n setErrors(fieldErrors)\n const firstMessage = Object.values(fieldErrors)[0]\n if (firstMessage) flash(firstMessage, 'error')\n return\n }\n\n const customFieldErrors = validateCustomFields(merged)\n if (Object.keys(customFieldErrors).length) {\n setErrors(customFieldErrors)\n const firstMessage = Object.values(customFieldErrors)[0]\n if (firstMessage) flash(firstMessage, 'error')\n return\n }\n\n setErrors({})\n setIsSubmitting(true)\n try {\n const data = parsed.data\n const expectedCloseAt =\n data.expectedCloseAt && data.expectedCloseAt.length\n ? new Date(data.expectedCloseAt).toISOString()\n : undefined\n const payload: Record<string, unknown> = {\n title: data.title,\n status: data.status || undefined,\n pipelineId: data.pipelineId || undefined,\n pipelineStageId: data.pipelineStageId || undefined,\n valueAmount: typeof data.valueAmount === 'number' ? data.valueAmount : undefined,\n valueCurrency: data.valueCurrency || undefined,\n probability: typeof data.probability === 'number' ? data.probability : undefined,\n expectedCloseAt,\n description: data.description && data.description.length ? data.description : undefined,\n personIds: values.personIds.length ? values.personIds : undefined,\n companyIds: values.companyIds.length ? values.companyIds : undefined,\n }\n const custom = collectNormalizedCustomValues(merged)\n if (Object.keys(custom).length) payload.customFields = custom\n\n await runMutation({\n operation: () =>\n createCrud('customers/deals', payload, {\n errorMessage: tr('customers.deals.create.error', 'Failed to create deal.'),\n }),\n context: { formId: CONTEXT_ID, resourceKind: 'customers.deal', retryLastMutation },\n mutationPayload: payload,\n })\n flash(tr('customers.people.detail.deals.success', 'Deal created.'), 'success')\n router.push(returnTo)\n } catch (err) {\n const message = err instanceof Error ? err.message : tr('customers.deals.create.error', 'Failed to create deal.')\n flash(message, 'error')\n } finally {\n setIsSubmitting(false)\n }\n }, [\n collectNormalizedCustomValues,\n customFieldsLoaded,\n customValues,\n isSubmitting,\n retryLastMutation,\n returnTo,\n router,\n runMutation,\n tr,\n validateCustomFields,\n values,\n ])\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLFormElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n handleSubmit()\n }\n },\n [handleSubmit],\n )\n\n const onFormSubmit = React.useCallback(\n (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n handleSubmit()\n },\n [handleSubmit],\n )\n\n const cancelLabel = tr('customers.deals.create.cancel', 'Cancel')\n const submitLabel = tr('customers.deals.create.submit', 'Create deal')\n const submitDisabled = !customFieldsLoaded\n\n return (\n <form className=\"mx-auto max-w-screen-2xl\" onKeyDown={onKeyDown} onSubmit={onFormSubmit} noValidate>\n <FormHeader\n backHref={returnTo}\n backLabel={tr('customers.deals.create.back', 'Back to deals')}\n />\n\n <div className=\"mt-6 grid grid-cols-1 gap-5 lg:grid-cols-[minmax(0,1fr)_330px]\">\n <div className=\"space-y-4\">\n <DealSectionCard\n icon={Briefcase}\n title={tr('customers.deals.create.title', 'Create deal')}\n subtitle={tr('customers.deals.create.sections.details.subtitle', 'Core opportunity info')}\n actions={\n <>\n <Button type=\"button\" variant=\"outline\" onClick={handleCancel} disabled={isSubmitting}>\n {cancelLabel}\n </Button>\n <Button type=\"button\" onClick={handleSubmit} disabled={isSubmitting || submitDisabled}>\n {isSubmitting ? <Spinner className=\"size-4\" /> : <Save className=\"size-4\" />}\n {submitLabel}\n </Button>\n </>\n }\n >\n <DealDetailsFields\n values={values}\n errors={errors}\n isSubmitting={isSubmitting}\n patch={patch}\n onPipelineChange={handlePipelineChange}\n pipelines={pipelines}\n stages={stages}\n statusLabels={statusLabels}\n tr={tr}\n />\n </DealSectionCard>\n\n <DealAssociationsSection\n tr={tr}\n personIds={values.personIds}\n companyIds={values.companyIds}\n onPeopleChange={(next) => patch({ personIds: next })}\n onCompaniesChange={(next) => patch({ companyIds: next })}\n disabled={isSubmitting}\n />\n </div>\n\n <DealCreateSidebar\n tr={tr}\n customValues={customValues}\n onCustomChange={handleCustomChange}\n errors={errors}\n disabled={isSubmitting}\n customCount={customCount}\n manageHref={CUSTOM_FIELDS_MANAGE_HREF}\n onCustomLoaded={handleCustomAttributesLoaded}\n />\n </div>\n </form>\n )\n}\n\nexport default CreateDealForm\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Building2, X } from "lucide-react";
|
|
5
|
+
import { Avatar } from "@open-mercato/ui/primitives/avatar";
|
|
6
|
+
import { Button } from "@open-mercato/ui/primitives/button";
|
|
7
|
+
import { IconButton } from "@open-mercato/ui/primitives/icon-button";
|
|
8
|
+
import { SearchInput } from "@open-mercato/ui/primitives/search-input";
|
|
9
|
+
import { useDealAssociationLookups } from "../DealForm.js";
|
|
10
|
+
function sanitizeIdList(input) {
|
|
11
|
+
if (!Array.isArray(input)) return [];
|
|
12
|
+
const set = /* @__PURE__ */ new Set();
|
|
13
|
+
input.forEach((candidate) => {
|
|
14
|
+
if (typeof candidate !== "string") return;
|
|
15
|
+
const trimmed = candidate.trim();
|
|
16
|
+
if (!trimmed.length) return;
|
|
17
|
+
set.add(trimmed);
|
|
18
|
+
});
|
|
19
|
+
return Array.from(set);
|
|
20
|
+
}
|
|
21
|
+
function DealAssociationsField({
|
|
22
|
+
id,
|
|
23
|
+
kind,
|
|
24
|
+
value,
|
|
25
|
+
onChange,
|
|
26
|
+
disabled = false,
|
|
27
|
+
labels
|
|
28
|
+
}) {
|
|
29
|
+
const lookups = useDealAssociationLookups();
|
|
30
|
+
const search = kind === "people" ? lookups.searchPeople : lookups.searchCompanies;
|
|
31
|
+
const fetchByIds = kind === "people" ? lookups.fetchPeopleByIds : lookups.fetchCompaniesByIds;
|
|
32
|
+
const [input, setInput] = React.useState("");
|
|
33
|
+
const [suggestions, setSuggestions] = React.useState([]);
|
|
34
|
+
const [cache, setCache] = React.useState(() => /* @__PURE__ */ new Map());
|
|
35
|
+
const [loading, setLoading] = React.useState(false);
|
|
36
|
+
const [error, setError] = React.useState(null);
|
|
37
|
+
const normalizedValue = React.useMemo(() => sanitizeIdList(value), [value]);
|
|
38
|
+
React.useEffect(() => {
|
|
39
|
+
if (!normalizedValue.length) return;
|
|
40
|
+
const missing = normalizedValue.filter((id2) => !cache.has(id2));
|
|
41
|
+
if (!missing.length) return;
|
|
42
|
+
let cancelled = false;
|
|
43
|
+
(async () => {
|
|
44
|
+
try {
|
|
45
|
+
const entries = await fetchByIds(missing);
|
|
46
|
+
if (cancelled) return;
|
|
47
|
+
setCache((prev) => {
|
|
48
|
+
const next = new Map(prev);
|
|
49
|
+
entries.forEach((entry) => {
|
|
50
|
+
if (entry?.id) next.set(entry.id, entry);
|
|
51
|
+
});
|
|
52
|
+
return next;
|
|
53
|
+
});
|
|
54
|
+
} catch {
|
|
55
|
+
if (!cancelled) setError(labels.error);
|
|
56
|
+
}
|
|
57
|
+
})().catch(() => {
|
|
58
|
+
});
|
|
59
|
+
return () => {
|
|
60
|
+
cancelled = true;
|
|
61
|
+
};
|
|
62
|
+
}, [cache, fetchByIds, labels.error, normalizedValue]);
|
|
63
|
+
React.useEffect(() => {
|
|
64
|
+
const query = input.trim();
|
|
65
|
+
if (disabled || query.length === 0) {
|
|
66
|
+
setLoading(false);
|
|
67
|
+
setSuggestions([]);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
let cancelled = false;
|
|
71
|
+
const handler = window.setTimeout(async () => {
|
|
72
|
+
setLoading(true);
|
|
73
|
+
try {
|
|
74
|
+
const results = await search(query);
|
|
75
|
+
if (cancelled) return;
|
|
76
|
+
setSuggestions(results);
|
|
77
|
+
setCache((prev) => {
|
|
78
|
+
const next = new Map(prev);
|
|
79
|
+
results.forEach((entry) => {
|
|
80
|
+
if (entry?.id) next.set(entry.id, entry);
|
|
81
|
+
});
|
|
82
|
+
return next;
|
|
83
|
+
});
|
|
84
|
+
setError(null);
|
|
85
|
+
} catch {
|
|
86
|
+
if (!cancelled) {
|
|
87
|
+
setError(labels.error);
|
|
88
|
+
setSuggestions([]);
|
|
89
|
+
}
|
|
90
|
+
} finally {
|
|
91
|
+
if (!cancelled) setLoading(false);
|
|
92
|
+
}
|
|
93
|
+
}, 200);
|
|
94
|
+
return () => {
|
|
95
|
+
cancelled = true;
|
|
96
|
+
window.clearTimeout(handler);
|
|
97
|
+
};
|
|
98
|
+
}, [disabled, input, labels.error, search]);
|
|
99
|
+
const filteredSuggestions = React.useMemo(
|
|
100
|
+
() => suggestions.filter((option) => !normalizedValue.includes(option.id)),
|
|
101
|
+
[normalizedValue, suggestions]
|
|
102
|
+
);
|
|
103
|
+
const selectedOptions = React.useMemo(
|
|
104
|
+
() => normalizedValue.map((id2) => cache.get(id2) ?? { id: id2, label: id2 }),
|
|
105
|
+
[cache, normalizedValue]
|
|
106
|
+
);
|
|
107
|
+
const addOption = React.useCallback(
|
|
108
|
+
(option) => {
|
|
109
|
+
if (!option?.id) return;
|
|
110
|
+
if (normalizedValue.includes(option.id)) return;
|
|
111
|
+
onChange([...normalizedValue, option.id]);
|
|
112
|
+
setCache((prev) => {
|
|
113
|
+
const next = new Map(prev);
|
|
114
|
+
next.set(option.id, option);
|
|
115
|
+
return next;
|
|
116
|
+
});
|
|
117
|
+
setInput("");
|
|
118
|
+
setSuggestions([]);
|
|
119
|
+
},
|
|
120
|
+
[normalizedValue, onChange]
|
|
121
|
+
);
|
|
122
|
+
const removeOption = React.useCallback(
|
|
123
|
+
(id2) => {
|
|
124
|
+
onChange(normalizedValue.filter((candidate) => candidate !== id2));
|
|
125
|
+
},
|
|
126
|
+
[normalizedValue, onChange]
|
|
127
|
+
);
|
|
128
|
+
const renderLeading = React.useCallback(
|
|
129
|
+
(option) => kind === "people" ? /* @__PURE__ */ jsx(Avatar, { label: option.label, size: "xs" }) : /* @__PURE__ */ jsx(Building2, { className: "size-3.5 text-muted-foreground", "aria-hidden": "true" }),
|
|
130
|
+
[kind]
|
|
131
|
+
);
|
|
132
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
133
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 rounded-md border border-input bg-background p-2", children: [
|
|
134
|
+
selectedOptions.length ? /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: selectedOptions.map((option) => /* @__PURE__ */ jsxs(
|
|
135
|
+
"span",
|
|
136
|
+
{
|
|
137
|
+
className: "inline-flex items-center gap-1.5 rounded-md bg-muted px-2 py-1 text-xs text-foreground",
|
|
138
|
+
children: [
|
|
139
|
+
renderLeading(option),
|
|
140
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: option.label }),
|
|
141
|
+
/* @__PURE__ */ jsx(
|
|
142
|
+
IconButton,
|
|
143
|
+
{
|
|
144
|
+
type: "button",
|
|
145
|
+
variant: "ghost",
|
|
146
|
+
size: "xs",
|
|
147
|
+
"aria-label": `${labels.remove} ${option.label}`,
|
|
148
|
+
onClick: () => removeOption(option.id),
|
|
149
|
+
disabled,
|
|
150
|
+
children: /* @__PURE__ */ jsx(X, { className: "size-3" })
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
]
|
|
154
|
+
},
|
|
155
|
+
option.id
|
|
156
|
+
)) }) : null,
|
|
157
|
+
/* @__PURE__ */ jsx(
|
|
158
|
+
SearchInput,
|
|
159
|
+
{
|
|
160
|
+
id,
|
|
161
|
+
size: "default",
|
|
162
|
+
value: input,
|
|
163
|
+
onChange: setInput,
|
|
164
|
+
placeholder: labels.placeholder,
|
|
165
|
+
disabled,
|
|
166
|
+
onKeyDown: (event) => {
|
|
167
|
+
if (event.key === "Enter") {
|
|
168
|
+
event.preventDefault();
|
|
169
|
+
const nextOption = filteredSuggestions[0];
|
|
170
|
+
if (nextOption) addOption(nextOption);
|
|
171
|
+
} else if (event.key === "Backspace" && !input.length && normalizedValue.length) {
|
|
172
|
+
removeOption(normalizedValue[normalizedValue.length - 1]);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
] }),
|
|
178
|
+
loading ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: labels.loading }) : null,
|
|
179
|
+
!loading && input.trim().length > 0 && filteredSuggestions.length ? /* @__PURE__ */ jsx("div", { className: "flex max-h-72 flex-col gap-1 overflow-y-auto rounded-md border border-border bg-popover p-1 shadow-md", children: filteredSuggestions.slice(0, 8).map((option) => /* @__PURE__ */ jsxs(
|
|
180
|
+
Button,
|
|
181
|
+
{
|
|
182
|
+
type: "button",
|
|
183
|
+
variant: "ghost",
|
|
184
|
+
size: "sm",
|
|
185
|
+
className: "h-auto justify-start px-2 py-1 text-left font-normal",
|
|
186
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
187
|
+
onClick: () => addOption(option),
|
|
188
|
+
disabled,
|
|
189
|
+
"aria-label": option.label,
|
|
190
|
+
children: [
|
|
191
|
+
renderLeading(option),
|
|
192
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col items-start", children: [
|
|
193
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-sm", children: option.label }),
|
|
194
|
+
option.subtitle ? /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: option.subtitle }) : null
|
|
195
|
+
] })
|
|
196
|
+
]
|
|
197
|
+
},
|
|
198
|
+
option.id
|
|
199
|
+
)) }) : null,
|
|
200
|
+
!loading && input.trim().length > 0 && !filteredSuggestions.length ? /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: labels.noResults }) : null,
|
|
201
|
+
error ? /* @__PURE__ */ jsx("div", { className: "text-xs text-status-error-text", children: error }) : null
|
|
202
|
+
] });
|
|
203
|
+
}
|
|
204
|
+
var DealAssociationsField_default = DealAssociationsField;
|
|
205
|
+
export {
|
|
206
|
+
DealAssociationsField,
|
|
207
|
+
DealAssociationsField_default as default
|
|
208
|
+
};
|
|
209
|
+
//# sourceMappingURL=DealAssociationsField.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/customers/components/detail/create/DealAssociationsField.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Building2, X } from 'lucide-react'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { SearchInput } from '@open-mercato/ui/primitives/search-input'\nimport { useDealAssociationLookups } from '../DealForm'\n\ntype AssociationOption = {\n id: string\n label: string\n subtitle?: string | null\n}\n\nexport type DealAssociationsFieldProps = {\n id?: string\n kind: 'people' | 'companies'\n value: string[]\n onChange: (next: string[]) => void\n disabled?: boolean\n labels: {\n placeholder: string\n empty: string\n loading: string\n noResults: string\n remove: string\n error: string\n }\n}\n\nfunction sanitizeIdList(input: unknown): string[] {\n if (!Array.isArray(input)) return []\n const set = new Set<string>()\n input.forEach((candidate) => {\n if (typeof candidate !== 'string') return\n const trimmed = candidate.trim()\n if (!trimmed.length) return\n set.add(trimmed)\n })\n return Array.from(set)\n}\n\nexport function DealAssociationsField({\n id,\n kind,\n value,\n onChange,\n disabled = false,\n labels,\n}: DealAssociationsFieldProps) {\n const lookups = useDealAssociationLookups()\n const search = kind === 'people' ? lookups.searchPeople : lookups.searchCompanies\n const fetchByIds = kind === 'people' ? lookups.fetchPeopleByIds : lookups.fetchCompaniesByIds\n\n const [input, setInput] = React.useState('')\n const [suggestions, setSuggestions] = React.useState<AssociationOption[]>([])\n const [cache, setCache] = React.useState<Map<string, AssociationOption>>(() => new Map())\n const [loading, setLoading] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const normalizedValue = React.useMemo(() => sanitizeIdList(value), [value])\n\n React.useEffect(() => {\n if (!normalizedValue.length) return\n const missing = normalizedValue.filter((id) => !cache.has(id))\n if (!missing.length) return\n let cancelled = false\n ;(async () => {\n try {\n const entries = await fetchByIds(missing)\n if (cancelled) return\n setCache((prev) => {\n const next = new Map(prev)\n entries.forEach((entry) => {\n if (entry?.id) next.set(entry.id, entry)\n })\n return next\n })\n } catch {\n if (!cancelled) setError(labels.error)\n }\n })().catch(() => {\n // The inner try/catch already surfaces failures via setError; this guards the IIFE promise only.\n })\n return () => {\n cancelled = true\n }\n }, [cache, fetchByIds, labels.error, normalizedValue])\n\n React.useEffect(() => {\n const query = input.trim()\n // Only query once the operator starts typing. Searching on an empty string\n // returns the first page of *every* person/company in the tenant \u2014 useless as\n // a suggestion list and a performance trap at scale (thousands of records).\n if (disabled || query.length === 0) {\n setLoading(false)\n setSuggestions([])\n return\n }\n let cancelled = false\n const handler = window.setTimeout(async () => {\n setLoading(true)\n try {\n const results = await search(query)\n if (cancelled) return\n setSuggestions(results)\n setCache((prev) => {\n const next = new Map(prev)\n results.forEach((entry) => {\n if (entry?.id) next.set(entry.id, entry)\n })\n return next\n })\n setError(null)\n } catch {\n if (!cancelled) {\n setError(labels.error)\n setSuggestions([])\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }, 200)\n return () => {\n cancelled = true\n window.clearTimeout(handler)\n }\n }, [disabled, input, labels.error, search])\n\n const filteredSuggestions = React.useMemo(\n () => suggestions.filter((option) => !normalizedValue.includes(option.id)),\n [normalizedValue, suggestions],\n )\n\n const selectedOptions = React.useMemo(\n () => normalizedValue.map((id) => cache.get(id) ?? { id, label: id }),\n [cache, normalizedValue],\n )\n\n const addOption = React.useCallback(\n (option: AssociationOption) => {\n if (!option?.id) return\n if (normalizedValue.includes(option.id)) return\n onChange([...normalizedValue, option.id])\n setCache((prev) => {\n const next = new Map(prev)\n next.set(option.id, option)\n return next\n })\n setInput('')\n setSuggestions([])\n },\n [normalizedValue, onChange],\n )\n\n const removeOption = React.useCallback(\n (id: string) => {\n onChange(normalizedValue.filter((candidate) => candidate !== id))\n },\n [normalizedValue, onChange],\n )\n\n const renderLeading = React.useCallback(\n (option: AssociationOption) =>\n kind === 'people' ? (\n <Avatar label={option.label} size=\"xs\" />\n ) : (\n <Building2 className=\"size-3.5 text-muted-foreground\" aria-hidden=\"true\" />\n ),\n [kind],\n )\n\n return (\n <div className=\"space-y-2\">\n <div className=\"flex flex-col gap-2 rounded-md border border-input bg-background p-2\">\n {selectedOptions.length ? (\n <div className=\"flex flex-wrap gap-2\">\n {selectedOptions.map((option) => (\n <span\n key={option.id}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-muted px-2 py-1 text-xs text-foreground\"\n >\n {renderLeading(option)}\n <span className=\"truncate\">{option.label}</span>\n <IconButton\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n aria-label={`${labels.remove} ${option.label}`}\n onClick={() => removeOption(option.id)}\n disabled={disabled}\n >\n <X className=\"size-3\" />\n </IconButton>\n </span>\n ))}\n </div>\n ) : null}\n <SearchInput\n id={id}\n size=\"default\"\n value={input}\n onChange={setInput}\n placeholder={labels.placeholder}\n disabled={disabled}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n const nextOption = filteredSuggestions[0]\n if (nextOption) addOption(nextOption)\n } else if (event.key === 'Backspace' && !input.length && normalizedValue.length) {\n removeOption(normalizedValue[normalizedValue.length - 1])\n }\n }}\n />\n </div>\n {loading ? <div className=\"text-xs text-muted-foreground\">{labels.loading}</div> : null}\n {!loading && input.trim().length > 0 && filteredSuggestions.length ? (\n <div className=\"flex max-h-72 flex-col gap-1 overflow-y-auto rounded-md border border-border bg-popover p-1 shadow-md\">\n {filteredSuggestions.slice(0, 8).map((option) => (\n <Button\n key={option.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto justify-start px-2 py-1 text-left font-normal\"\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => addOption(option)}\n disabled={disabled}\n aria-label={option.label}\n >\n {renderLeading(option)}\n <span className=\"flex min-w-0 flex-col items-start\">\n <span className=\"truncate text-sm\">{option.label}</span>\n {option.subtitle ? (\n <span className=\"truncate text-xs text-muted-foreground\">{option.subtitle}</span>\n ) : null}\n </span>\n </Button>\n ))}\n </div>\n ) : null}\n {!loading && input.trim().length > 0 && !filteredSuggestions.length ? (\n <div className=\"text-xs text-muted-foreground\">{labels.noResults}</div>\n ) : null}\n {error ? <div className=\"text-xs text-status-error-text\">{error}</div> : null}\n </div>\n )\n}\n\nexport default DealAssociationsField\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["id"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Users } from "lucide-react";
|
|
4
|
+
import { DealSectionCard } from "./DealSectionCard.js";
|
|
5
|
+
import { DealFormField } from "./DealFormField.js";
|
|
6
|
+
import { DealAssociationsField } from "./DealAssociationsField.js";
|
|
7
|
+
function DealAssociationsSection({
|
|
8
|
+
tr,
|
|
9
|
+
personIds,
|
|
10
|
+
companyIds,
|
|
11
|
+
onPeopleChange,
|
|
12
|
+
onCompaniesChange,
|
|
13
|
+
disabled
|
|
14
|
+
}) {
|
|
15
|
+
const peopleLabels = {
|
|
16
|
+
placeholder: tr("customers.deals.create.associations.peoplePlaceholder", "Search people by name or email\u2026"),
|
|
17
|
+
empty: tr("customers.deals.form.people.empty", "No people linked yet."),
|
|
18
|
+
loading: tr("customers.deals.form.people.loading", "Searching people\u2026"),
|
|
19
|
+
noResults: tr("customers.deals.form.people.noResults", "No people match your search."),
|
|
20
|
+
remove: tr("customers.deals.form.assignees.remove", "Remove"),
|
|
21
|
+
error: tr("customers.deals.form.people.error", "Failed to load people.")
|
|
22
|
+
};
|
|
23
|
+
const companyLabels = {
|
|
24
|
+
placeholder: tr("customers.deals.create.associations.companiesPlaceholder", "Search companies by name or domain\u2026"),
|
|
25
|
+
empty: tr("customers.deals.form.companies.empty", "No companies linked yet."),
|
|
26
|
+
loading: tr("customers.deals.form.companies.loading", "Searching companies\u2026"),
|
|
27
|
+
noResults: tr("customers.deals.form.companies.noResults", "No companies match your search."),
|
|
28
|
+
remove: tr("customers.deals.form.assignees.remove", "Remove"),
|
|
29
|
+
error: tr("customers.deals.form.companies.error", "Failed to load companies.")
|
|
30
|
+
};
|
|
31
|
+
return /* @__PURE__ */ jsxs(
|
|
32
|
+
DealSectionCard,
|
|
33
|
+
{
|
|
34
|
+
icon: Users,
|
|
35
|
+
title: tr("customers.deals.create.sections.associations.title", "Associations"),
|
|
36
|
+
subtitle: tr("customers.deals.create.sections.associations.subtitle", "Link people and companies to this deal"),
|
|
37
|
+
children: [
|
|
38
|
+
/* @__PURE__ */ jsx(DealFormField, { fieldId: "personIds", label: tr("customers.people.detail.deals.fields.people", "People"), children: /* @__PURE__ */ jsx(
|
|
39
|
+
DealAssociationsField,
|
|
40
|
+
{
|
|
41
|
+
kind: "people",
|
|
42
|
+
value: personIds,
|
|
43
|
+
onChange: onPeopleChange,
|
|
44
|
+
disabled,
|
|
45
|
+
labels: peopleLabels
|
|
46
|
+
}
|
|
47
|
+
) }),
|
|
48
|
+
/* @__PURE__ */ jsx(DealFormField, { fieldId: "companyIds", label: tr("customers.people.detail.deals.fields.companies", "Companies"), children: /* @__PURE__ */ jsx(
|
|
49
|
+
DealAssociationsField,
|
|
50
|
+
{
|
|
51
|
+
kind: "companies",
|
|
52
|
+
value: companyIds,
|
|
53
|
+
onChange: onCompaniesChange,
|
|
54
|
+
disabled,
|
|
55
|
+
labels: companyLabels
|
|
56
|
+
}
|
|
57
|
+
) })
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
var DealAssociationsSection_default = DealAssociationsSection;
|
|
63
|
+
export {
|
|
64
|
+
DealAssociationsSection,
|
|
65
|
+
DealAssociationsSection_default as default
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=DealAssociationsSection.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/customers/components/detail/create/DealAssociationsSection.tsx"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Users } from 'lucide-react'\nimport { DealSectionCard } from './DealSectionCard'\nimport { DealFormField } from './DealFormField'\nimport { DealAssociationsField } from './DealAssociationsField'\nimport type { Translate } from './dealFormTypes'\n\nexport type DealAssociationsSectionProps = {\n tr: Translate\n personIds: string[]\n companyIds: string[]\n onPeopleChange: (next: string[]) => void\n onCompaniesChange: (next: string[]) => void\n disabled: boolean\n}\n\nexport function DealAssociationsSection({\n tr,\n personIds,\n companyIds,\n onPeopleChange,\n onCompaniesChange,\n disabled,\n}: DealAssociationsSectionProps) {\n const peopleLabels = {\n placeholder: tr('customers.deals.create.associations.peoplePlaceholder', 'Search people by name or email\u2026'),\n empty: tr('customers.deals.form.people.empty', 'No people linked yet.'),\n loading: tr('customers.deals.form.people.loading', 'Searching people\u2026'),\n noResults: tr('customers.deals.form.people.noResults', 'No people match your search.'),\n remove: tr('customers.deals.form.assignees.remove', 'Remove'),\n error: tr('customers.deals.form.people.error', 'Failed to load people.'),\n }\n const companyLabels = {\n placeholder: tr('customers.deals.create.associations.companiesPlaceholder', 'Search companies by name or domain\u2026'),\n empty: tr('customers.deals.form.companies.empty', 'No companies linked yet.'),\n loading: tr('customers.deals.form.companies.loading', 'Searching companies\u2026'),\n noResults: tr('customers.deals.form.companies.noResults', 'No companies match your search.'),\n remove: tr('customers.deals.form.assignees.remove', 'Remove'),\n error: tr('customers.deals.form.companies.error', 'Failed to load companies.'),\n }\n\n return (\n <DealSectionCard\n icon={Users}\n title={tr('customers.deals.create.sections.associations.title', 'Associations')}\n subtitle={tr('customers.deals.create.sections.associations.subtitle', 'Link people and companies to this deal')}\n >\n <DealFormField fieldId=\"personIds\" label={tr('customers.people.detail.deals.fields.people', 'People')}>\n <DealAssociationsField\n kind=\"people\"\n value={personIds}\n onChange={onPeopleChange}\n disabled={disabled}\n labels={peopleLabels}\n />\n </DealFormField>\n <DealFormField fieldId=\"companyIds\" label={tr('customers.people.detail.deals.fields.companies', 'Companies')}>\n <DealAssociationsField\n kind=\"companies\"\n value={companyIds}\n onChange={onCompaniesChange}\n disabled={disabled}\n labels={companyLabels}\n />\n </DealFormField>\n </DealSectionCard>\n )\n}\n\nexport default DealAssociationsSection\n"],
|
|
5
|
+
"mappings": ";AA4CI,SAMI,KANJ;AAzCJ,SAAS,aAAa;AACtB,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,6BAA6B;AAY/B,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAiC;AAC/B,QAAM,eAAe;AAAA,IACnB,aAAa,GAAG,yDAAyD,sCAAiC;AAAA,IAC1G,OAAO,GAAG,qCAAqC,uBAAuB;AAAA,IACtE,SAAS,GAAG,uCAAuC,wBAAmB;AAAA,IACtE,WAAW,GAAG,yCAAyC,8BAA8B;AAAA,IACrF,QAAQ,GAAG,yCAAyC,QAAQ;AAAA,IAC5D,OAAO,GAAG,qCAAqC,wBAAwB;AAAA,EACzE;AACA,QAAM,gBAAgB;AAAA,IACpB,aAAa,GAAG,4DAA4D,0CAAqC;AAAA,IACjH,OAAO,GAAG,wCAAwC,0BAA0B;AAAA,IAC5E,SAAS,GAAG,0CAA0C,2BAAsB;AAAA,IAC5E,WAAW,GAAG,4CAA4C,iCAAiC;AAAA,IAC3F,QAAQ,GAAG,yCAAyC,QAAQ;AAAA,IAC5D,OAAO,GAAG,wCAAwC,2BAA2B;AAAA,EAC/E;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAM;AAAA,MACN,OAAO,GAAG,sDAAsD,cAAc;AAAA,MAC9E,UAAU,GAAG,yDAAyD,wCAAwC;AAAA,MAE9G;AAAA,4BAAC,iBAAc,SAAQ,aAAY,OAAO,GAAG,+CAA+C,QAAQ,GAClG;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,OAAO;AAAA,YACP,UAAU;AAAA,YACV;AAAA,YACA,QAAQ;AAAA;AAAA,QACV,GACF;AAAA,QACA,oBAAC,iBAAc,SAAQ,cAAa,OAAO,GAAG,kDAAkD,WAAW,GACzG;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,OAAO;AAAA,YACP,UAAU;AAAA,YACV;AAAA,YACA,QAAQ;AAAA;AAAA,QACV,GACF;AAAA;AAAA;AAAA,EACF;AAEJ;AAEA,IAAO,kCAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|