@open-mercato/core 0.6.6-develop.6386.1.391a1afb9e → 0.6.6-develop.6387.1.f4896ad565

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.
Files changed (57) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/customers/api/deals/map/route.js +433 -0
  3. package/dist/modules/customers/api/deals/map/route.js.map +7 -0
  4. package/dist/modules/customers/backend/customers/companies/create/page.js +2 -0
  5. package/dist/modules/customers/backend/customers/companies/create/page.js.map +2 -2
  6. package/dist/modules/customers/backend/customers/deals/map/components/DealsLocationPanel.js +159 -0
  7. package/dist/modules/customers/backend/customers/deals/map/components/DealsLocationPanel.js.map +7 -0
  8. package/dist/modules/customers/backend/customers/deals/map/components/DealsMapCanvas.js +26 -0
  9. package/dist/modules/customers/backend/customers/deals/map/components/DealsMapCanvas.js.map +7 -0
  10. package/dist/modules/customers/backend/customers/deals/map/components/DealsMapCanvasImpl.js +321 -0
  11. package/dist/modules/customers/backend/customers/deals/map/components/DealsMapCanvasImpl.js.map +7 -0
  12. package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js +533 -0
  13. package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js.map +7 -0
  14. package/dist/modules/customers/backend/customers/deals/map/page.js +55 -0
  15. package/dist/modules/customers/backend/customers/deals/map/page.js.map +7 -0
  16. package/dist/modules/customers/backend/customers/deals/map/page.meta.js +38 -0
  17. package/dist/modules/customers/backend/customers/deals/map/page.meta.js.map +7 -0
  18. package/dist/modules/customers/backend/customers/deals/pipeline/components/FilterBarRow.js +3 -3
  19. package/dist/modules/customers/backend/customers/deals/pipeline/components/FilterBarRow.js.map +2 -2
  20. package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js +22 -1
  21. package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js.map +2 -2
  22. package/dist/modules/customers/backend/customers/people/create/page.js +2 -0
  23. package/dist/modules/customers/backend/customers/people/create/page.js.map +2 -2
  24. package/dist/modules/customers/components/AddressEditor.js +34 -2
  25. package/dist/modules/customers/components/AddressEditor.js.map +2 -2
  26. package/dist/modules/customers/components/AddressTiles.js +43 -3
  27. package/dist/modules/customers/components/AddressTiles.js.map +2 -2
  28. package/dist/modules/customers/components/detail/AddressesSection.js +4 -1
  29. package/dist/modules/customers/components/detail/AddressesSection.js.map +2 -2
  30. package/dist/modules/customers/components/formConfig.js +10 -0
  31. package/dist/modules/customers/components/formConfig.js.map +2 -2
  32. package/dist/modules/customers/data/validators.js +3 -2
  33. package/dist/modules/customers/data/validators.js.map +2 -2
  34. package/dist/modules/customers/lib/dealsMapLocation.js +75 -0
  35. package/dist/modules/customers/lib/dealsMapLocation.js.map +7 -0
  36. package/package.json +11 -7
  37. package/src/modules/customers/api/deals/map/route.ts +549 -0
  38. package/src/modules/customers/backend/customers/companies/create/page.tsx +2 -0
  39. package/src/modules/customers/backend/customers/deals/map/components/DealsLocationPanel.tsx +213 -0
  40. package/src/modules/customers/backend/customers/deals/map/components/DealsMapCanvas.tsx +79 -0
  41. package/src/modules/customers/backend/customers/deals/map/components/DealsMapCanvasImpl.tsx +407 -0
  42. package/src/modules/customers/backend/customers/deals/map/components/DealsMapView.tsx +667 -0
  43. package/src/modules/customers/backend/customers/deals/map/page.meta.ts +36 -0
  44. package/src/modules/customers/backend/customers/deals/map/page.tsx +77 -0
  45. package/src/modules/customers/backend/customers/deals/pipeline/components/FilterBarRow.tsx +3 -3
  46. package/src/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx +22 -1
  47. package/src/modules/customers/backend/customers/people/create/page.tsx +2 -0
  48. package/src/modules/customers/components/AddressEditor.tsx +35 -0
  49. package/src/modules/customers/components/AddressTiles.tsx +51 -1
  50. package/src/modules/customers/components/detail/AddressesSection.tsx +3 -0
  51. package/src/modules/customers/components/formConfig.tsx +10 -0
  52. package/src/modules/customers/data/validators.ts +13 -2
  53. package/src/modules/customers/i18n/de.json +24 -0
  54. package/src/modules/customers/i18n/en.json +24 -0
  55. package/src/modules/customers/i18n/es.json +24 -0
  56. package/src/modules/customers/i18n/pl.json +24 -0
  57. package/src/modules/customers/lib/dealsMapLocation.ts +125 -0
@@ -0,0 +1,533 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { useQuery } from "@tanstack/react-query";
5
+ import { Button } from "@open-mercato/ui/primitives/button";
6
+ import { LoadingMessage, ErrorMessage } from "@open-mercato/ui/backend/detail";
7
+ import {
8
+ apiCall,
9
+ readApiResultOrThrow,
10
+ withScopedApiRequestHeaders
11
+ } from "@open-mercato/ui/backend/utils/apiCall";
12
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
13
+ import { translateWithFallback } from "@open-mercato/shared/lib/i18n/translate";
14
+ import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
15
+ import { FilterBarRow } from "../../pipeline/components/FilterBarRow.js";
16
+ import { StatusFilterPopover } from "../../pipeline/components/StatusFilterPopover.js";
17
+ import {
18
+ PipelineFilterPopover
19
+ } from "../../pipeline/components/PipelineFilterPopover.js";
20
+ import {
21
+ EntityFilterPopover
22
+ } from "../../pipeline/components/EntityFilterPopover.js";
23
+ import {
24
+ CloseDateFilterPopover
25
+ } from "../../pipeline/components/CloseDateFilterPopover.js";
26
+ import { SortByPopover } from "../../pipeline/components/SortByPopover.js";
27
+ import { fetchAssignableStaffMembers } from "../../../../../components/detail/assignableStaff.js";
28
+ import { DealsLocationPanel } from "./DealsLocationPanel.js";
29
+ import {
30
+ DealsMapCanvas
31
+ } from "./DealsMapCanvas.js";
32
+ const DEFAULT_SORT = "updated_desc";
33
+ const PAGE_SIZE = 100;
34
+ const MAX_DEALS = 500;
35
+ const FALLBACK_TONES = ["success", "warning", "error", "info", "neutral", "brand"];
36
+ const KNOWN_STAGE_TONES = /* @__PURE__ */ new Set([
37
+ "success",
38
+ "warning",
39
+ "info",
40
+ "error",
41
+ "neutral",
42
+ "brand",
43
+ "pink"
44
+ ]);
45
+ const SUPPRESS_AUTH_REDIRECT_HEADERS = {
46
+ "x-om-forbidden-redirect": "0",
47
+ "x-om-unauthorized-redirect": "0"
48
+ };
49
+ const NO_FILTER_CHIPS = [];
50
+ const noopChipClick = (_chipId) => {
51
+ };
52
+ function normalizeNumber(value) {
53
+ if (typeof value === "number" && Number.isFinite(value)) return value;
54
+ if (typeof value === "string" && value.trim().length) {
55
+ const parsed = Number(value);
56
+ return Number.isFinite(parsed) ? parsed : null;
57
+ }
58
+ return null;
59
+ }
60
+ function normalizeText(value) {
61
+ return typeof value === "string" && value.trim().length ? value.trim() : null;
62
+ }
63
+ function normalizeLocation(location) {
64
+ if (!location) return null;
65
+ const latitude = normalizeNumber(location.latitude);
66
+ const longitude = normalizeNumber(location.longitude);
67
+ if (latitude === null || longitude === null) return null;
68
+ return {
69
+ latitude,
70
+ longitude,
71
+ city: normalizeText(location.city),
72
+ region: normalizeText(location.region),
73
+ country: normalizeText(location.country)
74
+ };
75
+ }
76
+ function mapApiItem(item, fallbackTitle) {
77
+ const id = normalizeText(item.id);
78
+ if (!id) return null;
79
+ const firstCompany = Array.isArray(item.companies) ? item.companies.find((entry) => entry && normalizeText(entry.label)) : null;
80
+ return {
81
+ id,
82
+ title: normalizeText(item.title) ?? fallbackTitle,
83
+ status: normalizeText(item.status),
84
+ pipelineId: normalizeText(item.pipelineId),
85
+ pipelineStageId: normalizeText(item.pipelineStageId),
86
+ valueAmount: normalizeNumber(item.valueAmount),
87
+ valueCurrency: normalizeText(item.valueCurrency)?.toUpperCase() ?? null,
88
+ probability: normalizeNumber(item.probability),
89
+ expectedCloseAt: normalizeText(item.expectedCloseAt),
90
+ ownerUserId: normalizeText(item.ownerUserId),
91
+ companyLabel: firstCompany ? normalizeText(firstCompany.label) : null,
92
+ location: normalizeLocation(item.location)
93
+ };
94
+ }
95
+ function mapSortOptionToApi(option) {
96
+ switch (option) {
97
+ case "updated_desc":
98
+ return { sortField: "updatedAt", sortDir: "desc" };
99
+ case "updated_asc":
100
+ return { sortField: "updatedAt", sortDir: "asc" };
101
+ case "created_desc":
102
+ return { sortField: "createdAt", sortDir: "desc" };
103
+ case "value_desc":
104
+ return { sortField: "value", sortDir: "desc" };
105
+ case "value_asc":
106
+ return { sortField: "value", sortDir: "asc" };
107
+ case "probability_desc":
108
+ return { sortField: "probability", sortDir: "desc" };
109
+ case "close_asc":
110
+ return { sortField: "expectedCloseAt", sortDir: "asc" };
111
+ case "owner_asc":
112
+ default:
113
+ return null;
114
+ }
115
+ }
116
+ function buildStageMetaById(stages) {
117
+ const byPipeline = /* @__PURE__ */ new Map();
118
+ for (const stage of stages) {
119
+ const key = stage.pipelineId ?? "";
120
+ const bucket = byPipeline.get(key) ?? [];
121
+ bucket.push(stage);
122
+ byPipeline.set(key, bucket);
123
+ }
124
+ const meta = /* @__PURE__ */ new Map();
125
+ for (const bucket of byPipeline.values()) {
126
+ const sorted = bucket.slice().sort((a, b) => a.order - b.order);
127
+ sorted.forEach((stage, index) => {
128
+ const tone = stage.color && KNOWN_STAGE_TONES.has(stage.color) ? stage.color : FALLBACK_TONES[index % FALLBACK_TONES.length];
129
+ meta.set(stage.id, { label: stage.label, tone });
130
+ });
131
+ }
132
+ return meta;
133
+ }
134
+ function DealsMapView({ search }) {
135
+ const t = useT();
136
+ const scopeVersion = useOrganizationScopeVersion();
137
+ const [statusFilters, setStatusFilters] = React.useState([]);
138
+ const [selectedPipelineId, setSelectedPipelineId] = React.useState(null);
139
+ const [ownerFilters, setOwnerFilters] = React.useState([]);
140
+ const [peopleFilters, setPeopleFilters] = React.useState([]);
141
+ const [companyFilters, setCompanyFilters] = React.useState([]);
142
+ const [closeDateFilter, setCloseDateFilter] = React.useState({ from: null, to: null });
143
+ const [sortBy, setSortBy] = React.useState(DEFAULT_SORT);
144
+ const [ownerLabels, setOwnerLabels] = React.useState({});
145
+ const [peopleLabels, setPeopleLabels] = React.useState({});
146
+ const [companyLabels, setCompanyLabels] = React.useState({});
147
+ const [selectedDealId, setSelectedDealId] = React.useState(null);
148
+ const [mapCenter, setMapCenter] = React.useState(null);
149
+ const [debouncedSearch, setDebouncedSearch] = React.useState(search);
150
+ React.useEffect(() => {
151
+ const handle = window.setTimeout(() => setDebouncedSearch(search.trim()), 300);
152
+ return () => window.clearTimeout(handle);
153
+ }, [search]);
154
+ const fallbackTitle = translateWithFallback(t, "customers.deals.pipeline.untitled", "Untitled deal");
155
+ const loadErrorLabel = translateWithFallback(
156
+ t,
157
+ "customers.deals.map.loadError",
158
+ "Failed to load deals for the map."
159
+ );
160
+ const apiSort = React.useMemo(() => {
161
+ const mapped = mapSortOptionToApi(sortBy);
162
+ return mapped ?? { sortField: "updatedAt", sortDir: "desc" };
163
+ }, [sortBy]);
164
+ const pipelinesQuery = useQuery({
165
+ queryKey: ["customers", "deals", "map", "pipelines", `scope:${scopeVersion}`],
166
+ staleTime: 6e4,
167
+ queryFn: async () => {
168
+ const call = await apiCall("/api/customers/pipelines", {
169
+ headers: SUPPRESS_AUTH_REDIRECT_HEADERS
170
+ });
171
+ if (!call.ok) return [];
172
+ return Array.isArray(call.result?.items) ? call.result.items : [];
173
+ }
174
+ });
175
+ const stagesQuery = useQuery({
176
+ queryKey: ["customers", "deals", "map", "stages", `scope:${scopeVersion}`],
177
+ staleTime: 6e4,
178
+ queryFn: async () => {
179
+ const call = await apiCall("/api/customers/pipeline-stages", {
180
+ headers: SUPPRESS_AUTH_REDIRECT_HEADERS
181
+ });
182
+ if (!call.ok) return [];
183
+ return Array.isArray(call.result?.items) ? call.result.items : [];
184
+ }
185
+ });
186
+ const staffQuery = useQuery({
187
+ queryKey: ["customers", "deals", "map", "staff", `scope:${scopeVersion}`],
188
+ staleTime: 3e5,
189
+ queryFn: async () => {
190
+ try {
191
+ const members = await withScopedApiRequestHeaders(
192
+ { ...SUPPRESS_AUTH_REDIRECT_HEADERS },
193
+ () => fetchAssignableStaffMembers("", { pageSize: 100 })
194
+ );
195
+ const names = /* @__PURE__ */ new Map();
196
+ for (const member of members) {
197
+ if (member.userId && member.displayName) names.set(member.userId, member.displayName);
198
+ }
199
+ return names;
200
+ } catch {
201
+ return /* @__PURE__ */ new Map();
202
+ }
203
+ }
204
+ });
205
+ const ownerNamesById = staffQuery.data ?? null;
206
+ const dealsQuery = useQuery({
207
+ queryKey: [
208
+ "customers",
209
+ "deals",
210
+ "map",
211
+ "deals",
212
+ `scope:${scopeVersion}`,
213
+ `search:${debouncedSearch}`,
214
+ `status:${statusFilters.slice().sort().join(",")}`,
215
+ `pipeline:${selectedPipelineId ?? ""}`,
216
+ `owners:${ownerFilters.slice().sort().join(",")}`,
217
+ `people:${peopleFilters.slice().sort().join(",")}`,
218
+ `companies:${companyFilters.slice().sort().join(",")}`,
219
+ `close:${closeDateFilter.from ?? ""}-${closeDateFilter.to ?? ""}`,
220
+ `sort:${apiSort.sortField}:${apiSort.sortDir}`
221
+ ],
222
+ staleTime: 3e4,
223
+ queryFn: async () => {
224
+ const buildParams = (page) => {
225
+ const params = new URLSearchParams();
226
+ params.set("page", String(page));
227
+ params.set("pageSize", String(PAGE_SIZE));
228
+ params.set("sortField", apiSort.sortField);
229
+ params.set("sortDir", apiSort.sortDir);
230
+ if (debouncedSearch.length) params.set("search", debouncedSearch);
231
+ for (const status of statusFilters) params.append("status", status);
232
+ if (selectedPipelineId) params.set("pipelineId", selectedPipelineId);
233
+ for (const ownerId of ownerFilters) params.append("ownerUserId", ownerId);
234
+ for (const personId of peopleFilters) params.append("personId", personId);
235
+ for (const companyId of companyFilters) params.append("companyId", companyId);
236
+ if (closeDateFilter.from) params.set("expectedCloseAtFrom", closeDateFilter.from);
237
+ if (closeDateFilter.to) params.set("expectedCloseAtTo", closeDateFilter.to);
238
+ return params;
239
+ };
240
+ const fetchPage = (page) => readApiResultOrThrow(
241
+ `/api/customers/deals/map?${buildParams(page).toString()}`,
242
+ void 0,
243
+ { errorMessage: loadErrorLabel }
244
+ );
245
+ const collected = [];
246
+ const collectFrom = (payload) => {
247
+ const items = Array.isArray(payload?.items) ? payload.items : [];
248
+ for (const item of items) {
249
+ const mapped = mapApiItem(item, fallbackTitle);
250
+ if (mapped) collected.push(mapped);
251
+ }
252
+ };
253
+ const firstPage = await fetchPage(1);
254
+ collectFrom(firstPage);
255
+ const total = typeof firstPage?.total === "number" ? firstPage.total : collected.length;
256
+ const reportedTotalPages = typeof firstPage?.totalPages === "number" ? firstPage.totalPages : 1;
257
+ const maxPages = Math.max(1, Math.ceil(MAX_DEALS / PAGE_SIZE));
258
+ const lastPage = Math.min(reportedTotalPages, maxPages);
259
+ if (lastPage > 1) {
260
+ const rest = await Promise.all(
261
+ Array.from({ length: lastPage - 1 }, (_unused, index) => fetchPage(index + 2))
262
+ );
263
+ for (const payload of rest) collectFrom(payload);
264
+ }
265
+ const deals2 = collected.slice(0, MAX_DEALS);
266
+ return { deals: deals2, total, truncated: total > deals2.length };
267
+ }
268
+ });
269
+ const stageMetaById = React.useMemo(
270
+ () => buildStageMetaById(stagesQuery.data ?? []),
271
+ [stagesQuery.data]
272
+ );
273
+ const deals = React.useMemo(() => {
274
+ const fetched = dealsQuery.data?.deals ?? [];
275
+ if (sortBy !== "owner_asc" || !ownerNamesById) return fetched;
276
+ return fetched.slice().sort((a, b) => {
277
+ const aLabel = (a.ownerUserId ? ownerNamesById.get(a.ownerUserId) : null) ?? a.ownerUserId ?? "";
278
+ const bLabel = (b.ownerUserId ? ownerNamesById.get(b.ownerUserId) : null) ?? b.ownerUserId ?? "";
279
+ return aLabel.localeCompare(bLabel);
280
+ });
281
+ }, [dealsQuery.data, sortBy, ownerNamesById]);
282
+ const locatedDeals = React.useMemo(
283
+ () => deals.filter((deal) => deal.location !== null),
284
+ [deals]
285
+ );
286
+ const canvasDeals = React.useMemo(
287
+ () => locatedDeals.map((deal) => ({
288
+ id: deal.id,
289
+ latitude: deal.location.latitude,
290
+ longitude: deal.location.longitude,
291
+ tone: deal.pipelineStageId ? stageMetaById.get(deal.pipelineStageId)?.tone ?? null : null
292
+ })),
293
+ [locatedDeals, stageMetaById]
294
+ );
295
+ const legendStages = React.useMemo(() => {
296
+ if (stageMetaById.size === 0) return [];
297
+ const presentStageIds = /* @__PURE__ */ new Set();
298
+ for (const deal of locatedDeals) {
299
+ if (deal.pipelineStageId) presentStageIds.add(deal.pipelineStageId);
300
+ }
301
+ const stages = [];
302
+ for (const stage of stagesQuery.data ?? []) {
303
+ if (!presentStageIds.has(stage.id)) continue;
304
+ const meta = stageMetaById.get(stage.id);
305
+ if (!meta) continue;
306
+ stages.push({ id: stage.id, label: meta.label, tone: meta.tone });
307
+ }
308
+ return stages;
309
+ }, [locatedDeals, stageMetaById, stagesQuery.data]);
310
+ const selectedDeal = React.useMemo(
311
+ () => selectedDealId ? deals.find((deal) => deal.id === selectedDealId) ?? null : null,
312
+ [deals, selectedDealId]
313
+ );
314
+ const preview = React.useMemo(() => {
315
+ if (!selectedDeal) return null;
316
+ const stageMeta = selectedDeal.pipelineStageId ? stageMetaById.get(selectedDeal.pipelineStageId) ?? null : null;
317
+ const locationLine = selectedDeal.location ? [selectedDeal.location.city, selectedDeal.location.region].filter(Boolean).join(", ") || selectedDeal.location.country : null;
318
+ return {
319
+ id: selectedDeal.id,
320
+ title: selectedDeal.title,
321
+ companyLabel: selectedDeal.companyLabel,
322
+ locationLine: locationLine || null,
323
+ valueAmount: selectedDeal.valueAmount,
324
+ valueCurrency: selectedDeal.valueCurrency,
325
+ probability: selectedDeal.probability,
326
+ expectedCloseAt: selectedDeal.expectedCloseAt,
327
+ stageLabel: stageMeta?.label ?? null,
328
+ stageTone: stageMeta?.tone ?? null,
329
+ ownerName: selectedDeal.ownerUserId && ownerNamesById ? ownerNamesById.get(selectedDeal.ownerUserId) ?? null : null
330
+ };
331
+ }, [selectedDeal, stageMetaById, ownerNamesById]);
332
+ const handleSelect = React.useCallback((dealId) => {
333
+ setSelectedDealId(dealId);
334
+ }, []);
335
+ const handleCenterChange = React.useCallback((center) => {
336
+ setMapCenter(center);
337
+ }, []);
338
+ const pipelineFilterOptions = React.useMemo(
339
+ () => (pipelinesQuery.data ?? []).map((pipeline) => ({ id: pipeline.id, name: pipeline.name })),
340
+ [pipelinesQuery.data]
341
+ );
342
+ const loadOwnerOptions = React.useCallback(
343
+ async (query, _signal) => {
344
+ try {
345
+ const members = await withScopedApiRequestHeaders(
346
+ { ...SUPPRESS_AUTH_REDIRECT_HEADERS },
347
+ () => fetchAssignableStaffMembers(query ?? "", { pageSize: 100 })
348
+ );
349
+ const options = members.filter((member) => !!member.userId && !!member.displayName).map((member) => ({ value: member.userId, label: member.displayName }));
350
+ setOwnerLabels((prev) => {
351
+ const next = { ...prev };
352
+ for (const option of options) next[option.value] = option.label;
353
+ return next;
354
+ });
355
+ return options;
356
+ } catch {
357
+ return [];
358
+ }
359
+ },
360
+ []
361
+ );
362
+ const loadPeopleOptions = React.useCallback(
363
+ async (query, signal) => {
364
+ const params = new URLSearchParams();
365
+ params.set("page", "1");
366
+ params.set("pageSize", "50");
367
+ if (query) params.set("search", query);
368
+ params.set("sortField", "displayName");
369
+ params.set("sortDir", "asc");
370
+ const call = await apiCall(`/api/customers/people?${params.toString()}`, {
371
+ signal,
372
+ headers: SUPPRESS_AUTH_REDIRECT_HEADERS
373
+ });
374
+ if (!call.ok) return [];
375
+ const items = call.result?.items ?? [];
376
+ const options = [];
377
+ for (const item of items) {
378
+ if (!item.id) continue;
379
+ const label = item.display_name && item.display_name.trim().length ? item.display_name.trim() : [item.first_name, item.last_name].filter(Boolean).join(" ").trim() || item.id.slice(0, 8);
380
+ options.push({ value: item.id, label });
381
+ }
382
+ setPeopleLabels((prev) => {
383
+ const next = { ...prev };
384
+ for (const option of options) next[option.value] = option.label;
385
+ return next;
386
+ });
387
+ return options;
388
+ },
389
+ []
390
+ );
391
+ const loadCompanyOptions = React.useCallback(
392
+ async (query, signal) => {
393
+ const params = new URLSearchParams();
394
+ params.set("page", "1");
395
+ params.set("pageSize", "50");
396
+ if (query) params.set("search", query);
397
+ params.set("sortField", "display_name");
398
+ params.set("sortDir", "asc");
399
+ const call = await apiCall(
400
+ `/api/customers/companies?${params.toString()}`,
401
+ { signal, headers: SUPPRESS_AUTH_REDIRECT_HEADERS }
402
+ );
403
+ if (!call.ok) return [];
404
+ const items = call.result?.items ?? [];
405
+ const options = [];
406
+ for (const item of items) {
407
+ if (!item.id || !item.display_name) continue;
408
+ options.push({ value: item.id, label: item.display_name });
409
+ }
410
+ setCompanyLabels((prev) => {
411
+ const next = { ...prev };
412
+ for (const option of options) next[option.value] = option.label;
413
+ return next;
414
+ });
415
+ return options;
416
+ },
417
+ []
418
+ );
419
+ const leadingChipsNode = /* @__PURE__ */ jsxs(Fragment, { children: [
420
+ /* @__PURE__ */ jsx(StatusFilterPopover, { values: statusFilters, onApply: setStatusFilters }),
421
+ /* @__PURE__ */ jsx(
422
+ PipelineFilterPopover,
423
+ {
424
+ pipelines: pipelineFilterOptions,
425
+ selectedPipelineId,
426
+ onApply: setSelectedPipelineId
427
+ }
428
+ ),
429
+ /* @__PURE__ */ jsx(
430
+ EntityFilterPopover,
431
+ {
432
+ label: translateWithFallback(t, "customers.deals.kanban.filter.owner", "Owner"),
433
+ anyLabel: translateWithFallback(t, "customers.deals.kanban.filter.all", "All"),
434
+ values: ownerFilters,
435
+ onApply: setOwnerFilters,
436
+ loadOptions: loadOwnerOptions,
437
+ labelById: ownerLabels
438
+ }
439
+ ),
440
+ /* @__PURE__ */ jsx(
441
+ EntityFilterPopover,
442
+ {
443
+ label: translateWithFallback(t, "customers.deals.kanban.filter.people", "People"),
444
+ values: peopleFilters,
445
+ onApply: setPeopleFilters,
446
+ loadOptions: loadPeopleOptions,
447
+ labelById: peopleLabels
448
+ }
449
+ ),
450
+ /* @__PURE__ */ jsx(
451
+ EntityFilterPopover,
452
+ {
453
+ label: translateWithFallback(t, "customers.deals.kanban.filter.companies", "Companies"),
454
+ values: companyFilters,
455
+ onApply: setCompanyFilters,
456
+ loadOptions: loadCompanyOptions,
457
+ labelById: companyLabels
458
+ }
459
+ ),
460
+ /* @__PURE__ */ jsx(CloseDateFilterPopover, { value: closeDateFilter, onApply: setCloseDateFilter })
461
+ ] });
462
+ const sortNode = /* @__PURE__ */ jsx(SortByPopover, { value: sortBy, onApply: setSortBy });
463
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
464
+ /* @__PURE__ */ jsx(
465
+ FilterBarRow,
466
+ {
467
+ leadingChips: leadingChipsNode,
468
+ chips: NO_FILTER_CHIPS,
469
+ sortNode,
470
+ onChipClick: noopChipClick
471
+ }
472
+ ),
473
+ dealsQuery.isLoading ? /* @__PURE__ */ jsx(
474
+ LoadingMessage,
475
+ {
476
+ label: translateWithFallback(t, "customers.deals.map.canvas.loading", "Loading map\u2026")
477
+ }
478
+ ) : dealsQuery.isError ? /* @__PURE__ */ jsx(
479
+ ErrorMessage,
480
+ {
481
+ label: dealsQuery.error instanceof Error ? dealsQuery.error.message : loadErrorLabel,
482
+ action: /* @__PURE__ */ jsx(
483
+ Button,
484
+ {
485
+ type: "button",
486
+ variant: "outline",
487
+ size: "sm",
488
+ onClick: () => void dealsQuery.refetch(),
489
+ children: translateWithFallback(t, "customers.deals.map.retry", "Retry")
490
+ }
491
+ )
492
+ }
493
+ ) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
494
+ dealsQuery.data?.truncated ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: translateWithFallback(
495
+ t,
496
+ "customers.deals.map.truncated",
497
+ "Showing first {count} of {total} located deals \u2014 refine filters to narrow the map.",
498
+ { count: deals.length, total: dealsQuery.data?.total ?? deals.length }
499
+ ) }) : null,
500
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-[480px] flex-col gap-4 lg:h-[calc(100vh-360px)] lg:flex-row", children: [
501
+ /* @__PURE__ */ jsx(
502
+ DealsLocationPanel,
503
+ {
504
+ deals,
505
+ locatedCount: dealsQuery.data?.total ?? deals.length,
506
+ stageMetaById,
507
+ mapCenter,
508
+ selectedDealId,
509
+ onSelect: handleSelect
510
+ }
511
+ ),
512
+ /* @__PURE__ */ jsx(
513
+ DealsMapCanvas,
514
+ {
515
+ className: "min-h-[480px] flex-1 overflow-hidden rounded-xl border border-border lg:min-h-0",
516
+ deals: canvasDeals,
517
+ legendStages,
518
+ preview,
519
+ selectedDealId,
520
+ onSelect: handleSelect,
521
+ onCenterChange: handleCenterChange
522
+ }
523
+ )
524
+ ] })
525
+ ] })
526
+ ] });
527
+ }
528
+ var DealsMapView_default = DealsMapView;
529
+ export {
530
+ DealsMapView,
531
+ DealsMapView_default as default
532
+ };
533
+ //# sourceMappingURL=DealsMapView.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../../src/modules/customers/backend/customers/deals/map/components/DealsMapView.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useQuery } from '@tanstack/react-query'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport {\n apiCall,\n readApiResultOrThrow,\n withScopedApiRequestHeaders,\n} from '@open-mercato/ui/backend/utils/apiCall'\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 type { FilterOptionTone } from '@open-mercato/shared/lib/query/advanced-filter'\nimport { FilterBarRow, type KanbanFilterChip } from '../../pipeline/components/FilterBarRow'\nimport { StatusFilterPopover } from '../../pipeline/components/StatusFilterPopover'\nimport {\n PipelineFilterPopover,\n type PipelineFilterOption,\n} from '../../pipeline/components/PipelineFilterPopover'\nimport {\n EntityFilterPopover,\n type EntityFilterOption,\n} from '../../pipeline/components/EntityFilterPopover'\nimport {\n CloseDateFilterPopover,\n type CloseDateRange,\n} from '../../pipeline/components/CloseDateFilterPopover'\nimport { SortByPopover, type SortOption } from '../../pipeline/components/SortByPopover'\nimport { fetchAssignableStaffMembers } from '../../../../../components/detail/assignableStaff'\nimport { DealsLocationPanel } from './DealsLocationPanel'\nimport {\n DealsMapCanvas,\n type DealsMapCanvasDeal,\n type DealsMapLegendStage,\n type DealsMapPreview,\n type MapCenter,\n} from './DealsMapCanvas'\n\nconst DEFAULT_SORT: SortOption = 'updated_desc'\nconst PAGE_SIZE = 100\nconst MAX_DEALS = 500\nconst FALLBACK_TONES: FilterOptionTone[] = ['success', 'warning', 'error', 'info', 'neutral', 'brand']\nconst KNOWN_STAGE_TONES: ReadonlySet<string> = new Set([\n 'success',\n 'warning',\n 'info',\n 'error',\n 'neutral',\n 'brand',\n 'pink',\n])\nconst SUPPRESS_AUTH_REDIRECT_HEADERS = {\n 'x-om-forbidden-redirect': '0',\n 'x-om-unauthorized-redirect': '0',\n} as const\n// The map has no quick-filter chips (filters live in the leading popovers), but FilterBarRow\n// requires the props \u2014 keep stable module-level references instead of per-render allocations.\nconst NO_FILTER_CHIPS: KanbanFilterChip[] = []\nconst noopChipClick = (_chipId: KanbanFilterChip['id']) => {}\n\ntype PipelineRecord = { id: string; name: string; isDefault: boolean }\n\ntype PipelineStageRecord = {\n id: string\n pipelineId: string\n label: string\n order: number\n color?: string | null\n}\n\nexport type StageMeta = { label: string; tone: FilterOptionTone }\n\ntype DealMapApiLocation = {\n latitude?: number | string | null\n longitude?: number | string | null\n city?: string | null\n region?: string | null\n country?: string | null\n source?: string | null\n}\n\ntype DealMapApiItem = {\n id?: string\n title?: string | null\n status?: string | null\n pipelineId?: string | null\n pipelineStageId?: string | null\n pipelineStage?: string | null\n valueAmount?: number | string | null\n valueCurrency?: string | null\n probability?: number | string | null\n expectedCloseAt?: string | null\n ownerUserId?: string | null\n updatedAt?: string | null\n companies?: Array<{ id?: string; label?: string } | null> | null\n people?: Array<{ id?: string; label?: string } | null> | null\n location?: DealMapApiLocation | null\n}\n\ntype DealsMapApiResponse = {\n items?: DealMapApiItem[]\n total?: number\n page?: number\n pageSize?: number\n totalPages?: number\n}\n\nexport type MapDealLocation = {\n latitude: number\n longitude: number\n city: string | null\n region: string | null\n country: string | null\n}\n\nexport type MapDeal = {\n id: string\n title: string\n status: string | null\n pipelineId: string | null\n pipelineStageId: string | null\n valueAmount: number | null\n valueCurrency: string | null\n probability: number | null\n expectedCloseAt: string | null\n ownerUserId: string | null\n companyLabel: string | null\n location: MapDealLocation | null\n}\n\ntype DealsMapQueryResult = {\n deals: MapDeal[]\n total: number\n truncated: boolean\n}\n\nfunction normalizeNumber(value: unknown): number | null {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && value.trim().length) {\n const parsed = Number(value)\n return Number.isFinite(parsed) ? parsed : null\n }\n return null\n}\n\nfunction normalizeText(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length ? value.trim() : null\n}\n\nfunction normalizeLocation(location: DealMapApiLocation | null | undefined): MapDealLocation | null {\n if (!location) return null\n const latitude = normalizeNumber(location.latitude)\n const longitude = normalizeNumber(location.longitude)\n if (latitude === null || longitude === null) return null\n return {\n latitude,\n longitude,\n city: normalizeText(location.city),\n region: normalizeText(location.region),\n country: normalizeText(location.country),\n }\n}\n\nfunction mapApiItem(item: DealMapApiItem, fallbackTitle: string): MapDeal | null {\n const id = normalizeText(item.id)\n if (!id) return null\n const firstCompany = Array.isArray(item.companies)\n ? item.companies.find((entry) => entry && normalizeText(entry.label))\n : null\n return {\n id,\n title: normalizeText(item.title) ?? fallbackTitle,\n status: normalizeText(item.status),\n pipelineId: normalizeText(item.pipelineId),\n pipelineStageId: normalizeText(item.pipelineStageId),\n valueAmount: normalizeNumber(item.valueAmount),\n valueCurrency: normalizeText(item.valueCurrency)?.toUpperCase() ?? null,\n probability: normalizeNumber(item.probability),\n expectedCloseAt: normalizeText(item.expectedCloseAt),\n ownerUserId: normalizeText(item.ownerUserId),\n companyLabel: firstCompany ? normalizeText(firstCompany.label) : null,\n location: normalizeLocation(item.location),\n }\n}\n\nfunction mapSortOptionToApi(option: SortOption): { sortField: string; sortDir: 'asc' | 'desc' } | null {\n switch (option) {\n case 'updated_desc':\n return { sortField: 'updatedAt', sortDir: 'desc' }\n case 'updated_asc':\n return { sortField: 'updatedAt', sortDir: 'asc' }\n case 'created_desc':\n return { sortField: 'createdAt', sortDir: 'desc' }\n case 'value_desc':\n return { sortField: 'value', sortDir: 'desc' }\n case 'value_asc':\n return { sortField: 'value', sortDir: 'asc' }\n case 'probability_desc':\n return { sortField: 'probability', sortDir: 'desc' }\n case 'close_asc':\n return { sortField: 'expectedCloseAt', sortDir: 'asc' }\n case 'owner_asc':\n default:\n return null\n }\n}\n\nfunction buildStageMetaById(stages: PipelineStageRecord[]): Map<string, StageMeta> {\n const byPipeline = new Map<string, PipelineStageRecord[]>()\n for (const stage of stages) {\n const key = stage.pipelineId ?? ''\n const bucket = byPipeline.get(key) ?? []\n bucket.push(stage)\n byPipeline.set(key, bucket)\n }\n const meta = new Map<string, StageMeta>()\n for (const bucket of byPipeline.values()) {\n const sorted = bucket.slice().sort((a, b) => a.order - b.order)\n sorted.forEach((stage, index) => {\n const tone =\n stage.color && KNOWN_STAGE_TONES.has(stage.color)\n ? (stage.color as FilterOptionTone)\n : FALLBACK_TONES[index % FALLBACK_TONES.length]\n meta.set(stage.id, { label: stage.label, tone })\n })\n }\n return meta\n}\n\ntype DealsMapViewProps = {\n search: string\n}\n\nexport function DealsMapView({ search }: DealsMapViewProps): React.ReactElement {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n\n const [statusFilters, setStatusFilters] = React.useState<string[]>([])\n const [selectedPipelineId, setSelectedPipelineId] = React.useState<string | null>(null)\n const [ownerFilters, setOwnerFilters] = React.useState<string[]>([])\n const [peopleFilters, setPeopleFilters] = React.useState<string[]>([])\n const [companyFilters, setCompanyFilters] = React.useState<string[]>([])\n const [closeDateFilter, setCloseDateFilter] = React.useState<CloseDateRange>({ from: null, to: null })\n const [sortBy, setSortBy] = React.useState<SortOption>(DEFAULT_SORT)\n const [ownerLabels, setOwnerLabels] = React.useState<Record<string, string>>({})\n const [peopleLabels, setPeopleLabels] = React.useState<Record<string, string>>({})\n const [companyLabels, setCompanyLabels] = React.useState<Record<string, string>>({})\n const [selectedDealId, setSelectedDealId] = React.useState<string | null>(null)\n const [mapCenter, setMapCenter] = React.useState<MapCenter | null>(null)\n\n const [debouncedSearch, setDebouncedSearch] = React.useState(search)\n React.useEffect(() => {\n const handle = window.setTimeout(() => setDebouncedSearch(search.trim()), 300)\n return () => window.clearTimeout(handle)\n }, [search])\n\n const fallbackTitle = translateWithFallback(t, 'customers.deals.pipeline.untitled', 'Untitled deal')\n const loadErrorLabel = translateWithFallback(\n t,\n 'customers.deals.map.loadError',\n 'Failed to load deals for the map.',\n )\n\n const apiSort = React.useMemo(() => {\n const mapped = mapSortOptionToApi(sortBy)\n return mapped ?? { sortField: 'updatedAt' as const, sortDir: 'desc' as const }\n }, [sortBy])\n\n const pipelinesQuery = useQuery<PipelineRecord[]>({\n queryKey: ['customers', 'deals', 'map', 'pipelines', `scope:${scopeVersion}`],\n staleTime: 60_000,\n queryFn: async () => {\n const call = await apiCall<{ items?: PipelineRecord[] }>('/api/customers/pipelines', {\n headers: SUPPRESS_AUTH_REDIRECT_HEADERS,\n })\n if (!call.ok) return []\n return Array.isArray(call.result?.items) ? call.result.items : []\n },\n })\n\n const stagesQuery = useQuery<PipelineStageRecord[]>({\n queryKey: ['customers', 'deals', 'map', 'stages', `scope:${scopeVersion}`],\n staleTime: 60_000,\n queryFn: async () => {\n const call = await apiCall<{ items?: PipelineStageRecord[] }>('/api/customers/pipeline-stages', {\n headers: SUPPRESS_AUTH_REDIRECT_HEADERS,\n })\n if (!call.ok) return []\n return Array.isArray(call.result?.items) ? call.result.items : []\n },\n })\n\n const staffQuery = useQuery<Map<string, string>>({\n queryKey: ['customers', 'deals', 'map', 'staff', `scope:${scopeVersion}`],\n staleTime: 300_000,\n queryFn: async () => {\n try {\n const members = await withScopedApiRequestHeaders(\n { ...SUPPRESS_AUTH_REDIRECT_HEADERS },\n () => fetchAssignableStaffMembers('', { pageSize: 100 }),\n )\n const names = new Map<string, string>()\n for (const member of members) {\n if (member.userId && member.displayName) names.set(member.userId, member.displayName)\n }\n return names\n } catch {\n return new Map<string, string>()\n }\n },\n })\n\n const ownerNamesById = staffQuery.data ?? null\n\n const dealsQuery = useQuery<DealsMapQueryResult>({\n queryKey: [\n 'customers',\n 'deals',\n 'map',\n 'deals',\n `scope:${scopeVersion}`,\n `search:${debouncedSearch}`,\n `status:${statusFilters.slice().sort().join(',')}`,\n `pipeline:${selectedPipelineId ?? ''}`,\n `owners:${ownerFilters.slice().sort().join(',')}`,\n `people:${peopleFilters.slice().sort().join(',')}`,\n `companies:${companyFilters.slice().sort().join(',')}`,\n `close:${closeDateFilter.from ?? ''}-${closeDateFilter.to ?? ''}`,\n `sort:${apiSort.sortField}:${apiSort.sortDir}`,\n ],\n staleTime: 30_000,\n queryFn: async () => {\n const buildParams = (page: number) => {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', String(PAGE_SIZE))\n params.set('sortField', apiSort.sortField)\n params.set('sortDir', apiSort.sortDir)\n if (debouncedSearch.length) params.set('search', debouncedSearch)\n for (const status of statusFilters) params.append('status', status)\n if (selectedPipelineId) params.set('pipelineId', selectedPipelineId)\n for (const ownerId of ownerFilters) params.append('ownerUserId', ownerId)\n for (const personId of peopleFilters) params.append('personId', personId)\n for (const companyId of companyFilters) params.append('companyId', companyId)\n if (closeDateFilter.from) params.set('expectedCloseAtFrom', closeDateFilter.from)\n if (closeDateFilter.to) params.set('expectedCloseAtTo', closeDateFilter.to)\n return params\n }\n const fetchPage = (page: number) =>\n readApiResultOrThrow<DealsMapApiResponse>(\n `/api/customers/deals/map?${buildParams(page).toString()}`,\n undefined,\n { errorMessage: loadErrorLabel },\n )\n\n const collected: MapDeal[] = []\n const collectFrom = (payload: DealsMapApiResponse | null | undefined) => {\n const items = Array.isArray(payload?.items) ? payload.items : []\n for (const item of items) {\n const mapped = mapApiItem(item, fallbackTitle)\n if (mapped) collected.push(mapped)\n }\n }\n\n // Fetch the first page to learn totalPages, then pull the remaining pages concurrently \u2014\n // the pages are independent, so awaiting them sequentially only stacked round-trip latency.\n const firstPage = await fetchPage(1)\n collectFrom(firstPage)\n const total = typeof firstPage?.total === 'number' ? firstPage.total : collected.length\n const reportedTotalPages = typeof firstPage?.totalPages === 'number' ? firstPage.totalPages : 1\n // Bound the fan-out: the client renders at most MAX_DEALS pins regardless of the located total.\n const maxPages = Math.max(1, Math.ceil(MAX_DEALS / PAGE_SIZE))\n const lastPage = Math.min(reportedTotalPages, maxPages)\n if (lastPage > 1) {\n const rest = await Promise.all(\n Array.from({ length: lastPage - 1 }, (_unused, index) => fetchPage(index + 2)),\n )\n for (const payload of rest) collectFrom(payload)\n }\n\n const deals = collected.slice(0, MAX_DEALS)\n return { deals, total, truncated: total > deals.length }\n },\n })\n\n const stageMetaById = React.useMemo(\n () => buildStageMetaById(stagesQuery.data ?? []),\n [stagesQuery.data],\n )\n\n const deals = React.useMemo(() => {\n const fetched = dealsQuery.data?.deals ?? []\n if (sortBy !== 'owner_asc' || !ownerNamesById) return fetched\n return fetched.slice().sort((a, b) => {\n const aLabel = (a.ownerUserId ? ownerNamesById.get(a.ownerUserId) : null) ?? a.ownerUserId ?? ''\n const bLabel = (b.ownerUserId ? ownerNamesById.get(b.ownerUserId) : null) ?? b.ownerUserId ?? ''\n return aLabel.localeCompare(bLabel)\n })\n }, [dealsQuery.data, sortBy, ownerNamesById])\n\n const locatedDeals = React.useMemo(\n () => deals.filter((deal): deal is MapDeal & { location: MapDealLocation } => deal.location !== null),\n [deals],\n )\n\n const canvasDeals = React.useMemo<DealsMapCanvasDeal[]>(\n () =>\n locatedDeals.map((deal) => ({\n id: deal.id,\n latitude: deal.location.latitude,\n longitude: deal.location.longitude,\n tone: deal.pipelineStageId ? stageMetaById.get(deal.pipelineStageId)?.tone ?? null : null,\n })),\n [locatedDeals, stageMetaById],\n )\n\n const legendStages = React.useMemo<DealsMapLegendStage[]>(() => {\n if (stageMetaById.size === 0) return []\n const presentStageIds = new Set<string>()\n for (const deal of locatedDeals) {\n if (deal.pipelineStageId) presentStageIds.add(deal.pipelineStageId)\n }\n const stages: DealsMapLegendStage[] = []\n for (const stage of stagesQuery.data ?? []) {\n if (!presentStageIds.has(stage.id)) continue\n const meta = stageMetaById.get(stage.id)\n if (!meta) continue\n stages.push({ id: stage.id, label: meta.label, tone: meta.tone })\n }\n return stages\n }, [locatedDeals, stageMetaById, stagesQuery.data])\n\n const selectedDeal = React.useMemo(\n () => (selectedDealId ? deals.find((deal) => deal.id === selectedDealId) ?? null : null),\n [deals, selectedDealId],\n )\n\n const preview = React.useMemo<DealsMapPreview | null>(() => {\n if (!selectedDeal) return null\n const stageMeta = selectedDeal.pipelineStageId\n ? stageMetaById.get(selectedDeal.pipelineStageId) ?? null\n : null\n const locationLine = selectedDeal.location\n ? [selectedDeal.location.city, selectedDeal.location.region].filter(Boolean).join(', ') ||\n selectedDeal.location.country\n : null\n return {\n id: selectedDeal.id,\n title: selectedDeal.title,\n companyLabel: selectedDeal.companyLabel,\n locationLine: locationLine || null,\n valueAmount: selectedDeal.valueAmount,\n valueCurrency: selectedDeal.valueCurrency,\n probability: selectedDeal.probability,\n expectedCloseAt: selectedDeal.expectedCloseAt,\n stageLabel: stageMeta?.label ?? null,\n stageTone: stageMeta?.tone ?? null,\n ownerName:\n selectedDeal.ownerUserId && ownerNamesById\n ? ownerNamesById.get(selectedDeal.ownerUserId) ?? null\n : null,\n }\n }, [selectedDeal, stageMetaById, ownerNamesById])\n\n const handleSelect = React.useCallback((dealId: string | null) => {\n setSelectedDealId(dealId)\n }, [])\n\n const handleCenterChange = React.useCallback((center: MapCenter) => {\n setMapCenter(center)\n }, [])\n\n const pipelineFilterOptions = React.useMemo<PipelineFilterOption[]>(\n () => (pipelinesQuery.data ?? []).map((pipeline) => ({ id: pipeline.id, name: pipeline.name })),\n [pipelinesQuery.data],\n )\n\n const loadOwnerOptions = React.useCallback(\n async (query: string, _signal: AbortSignal): Promise<EntityFilterOption[]> => {\n try {\n const members = await withScopedApiRequestHeaders(\n { ...SUPPRESS_AUTH_REDIRECT_HEADERS },\n () => fetchAssignableStaffMembers(query ?? '', { pageSize: 100 }),\n )\n const options: EntityFilterOption[] = members\n .filter((member) => !!member.userId && !!member.displayName)\n .map((member) => ({ value: member.userId, label: member.displayName }))\n setOwnerLabels((prev) => {\n const next: Record<string, string> = { ...prev }\n for (const option of options) next[option.value] = option.label\n return next\n })\n return options\n } catch {\n return []\n }\n },\n [],\n )\n\n const loadPeopleOptions = React.useCallback(\n async (query: string, signal: AbortSignal): Promise<EntityFilterOption[]> => {\n const params = new URLSearchParams()\n params.set('page', '1')\n params.set('pageSize', '50')\n if (query) params.set('search', query)\n params.set('sortField', 'displayName')\n params.set('sortDir', 'asc')\n const call = await apiCall<{\n items?: Array<{ id?: string; display_name?: string; first_name?: string; last_name?: string }>\n }>(`/api/customers/people?${params.toString()}`, {\n signal,\n headers: SUPPRESS_AUTH_REDIRECT_HEADERS,\n })\n if (!call.ok) return []\n const items = call.result?.items ?? []\n const options: EntityFilterOption[] = []\n for (const item of items) {\n if (!item.id) continue\n const label =\n item.display_name && item.display_name.trim().length\n ? item.display_name.trim()\n : [item.first_name, item.last_name].filter(Boolean).join(' ').trim() || item.id.slice(0, 8)\n options.push({ value: item.id, label })\n }\n setPeopleLabels((prev) => {\n const next: Record<string, string> = { ...prev }\n for (const option of options) next[option.value] = option.label\n return next\n })\n return options\n },\n [],\n )\n\n const loadCompanyOptions = React.useCallback(\n async (query: string, signal: AbortSignal): Promise<EntityFilterOption[]> => {\n const params = new URLSearchParams()\n params.set('page', '1')\n params.set('pageSize', '50')\n if (query) params.set('search', query)\n params.set('sortField', 'display_name')\n params.set('sortDir', 'asc')\n const call = await apiCall<{ items?: Array<{ id?: string; display_name?: string }> }>(\n `/api/customers/companies?${params.toString()}`,\n { signal, headers: SUPPRESS_AUTH_REDIRECT_HEADERS },\n )\n if (!call.ok) return []\n const items = call.result?.items ?? []\n const options: EntityFilterOption[] = []\n for (const item of items) {\n if (!item.id || !item.display_name) continue\n options.push({ value: item.id, label: item.display_name })\n }\n setCompanyLabels((prev) => {\n const next: Record<string, string> = { ...prev }\n for (const option of options) next[option.value] = option.label\n return next\n })\n return options\n },\n [],\n )\n\n const leadingChipsNode = (\n <>\n <StatusFilterPopover values={statusFilters} onApply={setStatusFilters} />\n <PipelineFilterPopover\n pipelines={pipelineFilterOptions}\n selectedPipelineId={selectedPipelineId}\n onApply={setSelectedPipelineId}\n />\n <EntityFilterPopover\n label={translateWithFallback(t, 'customers.deals.kanban.filter.owner', 'Owner')}\n anyLabel={translateWithFallback(t, 'customers.deals.kanban.filter.all', 'All')}\n values={ownerFilters}\n onApply={setOwnerFilters}\n loadOptions={loadOwnerOptions}\n labelById={ownerLabels}\n />\n <EntityFilterPopover\n label={translateWithFallback(t, 'customers.deals.kanban.filter.people', 'People')}\n values={peopleFilters}\n onApply={setPeopleFilters}\n loadOptions={loadPeopleOptions}\n labelById={peopleLabels}\n />\n <EntityFilterPopover\n label={translateWithFallback(t, 'customers.deals.kanban.filter.companies', 'Companies')}\n values={companyFilters}\n onApply={setCompanyFilters}\n loadOptions={loadCompanyOptions}\n labelById={companyLabels}\n />\n <CloseDateFilterPopover value={closeDateFilter} onApply={setCloseDateFilter} />\n </>\n )\n\n const sortNode = <SortByPopover value={sortBy} onApply={setSortBy} />\n\n return (\n <div className=\"flex flex-col gap-4\">\n <FilterBarRow\n leadingChips={leadingChipsNode}\n chips={NO_FILTER_CHIPS}\n sortNode={sortNode}\n onChipClick={noopChipClick}\n />\n\n {dealsQuery.isLoading ? (\n <LoadingMessage\n label={translateWithFallback(t, 'customers.deals.map.canvas.loading', 'Loading map\u2026')}\n />\n ) : dealsQuery.isError ? (\n <ErrorMessage\n label={dealsQuery.error instanceof Error ? dealsQuery.error.message : loadErrorLabel}\n action={\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => void dealsQuery.refetch()}\n >\n {translateWithFallback(t, 'customers.deals.map.retry', 'Retry')}\n </Button>\n }\n />\n ) : (\n <div className=\"flex flex-col gap-2\">\n {dealsQuery.data?.truncated ? (\n <p className=\"text-sm text-muted-foreground\">\n {translateWithFallback(\n t,\n 'customers.deals.map.truncated',\n 'Showing first {count} of {total} located deals \u2014 refine filters to narrow the map.',\n { count: deals.length, total: dealsQuery.data?.total ?? deals.length },\n )}\n </p>\n ) : null}\n <div className=\"flex min-h-[480px] flex-col gap-4 lg:h-[calc(100vh-360px)] lg:flex-row\">\n <DealsLocationPanel\n deals={deals}\n locatedCount={dealsQuery.data?.total ?? deals.length}\n stageMetaById={stageMetaById}\n mapCenter={mapCenter}\n selectedDealId={selectedDealId}\n onSelect={handleSelect}\n />\n <DealsMapCanvas\n className=\"min-h-[480px] flex-1 overflow-hidden rounded-xl border border-border lg:min-h-0\"\n deals={canvasDeals}\n legendStages={legendStages}\n preview={preview}\n selectedDealId={selectedDealId}\n onSelect={handleSelect}\n onCenterChange={handleCenterChange}\n />\n </div>\n </div>\n )}\n </div>\n )\n}\n\nexport default DealsMapView\n"],
5
+ "mappings": "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",
6
+ "names": ["deals"]
7
+ }