@open-mercato/core 0.4.5-develop-0f0e676c72 → 0.4.5-develop-8f98466993
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/generated/entities/customer_deal/index.js +4 -0
- package/dist/generated/entities/customer_deal/index.js.map +2 -2
- package/dist/generated/entities/customer_pipeline/index.js +17 -0
- package/dist/generated/entities/customer_pipeline/index.js.map +7 -0
- package/dist/generated/entities/customer_pipeline_stage/index.js +19 -0
- package/dist/generated/entities/customer_pipeline_stage/index.js.map +7 -0
- package/dist/generated/entities.ids.generated.js +2 -0
- package/dist/generated/entities.ids.generated.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +4 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/customers/acl.js +2 -0
- package/dist/modules/customers/acl.js.map +2 -2
- package/dist/modules/customers/api/deals/[id]/route.js +4 -0
- package/dist/modules/customers/api/deals/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/deals/route.js +12 -0
- package/dist/modules/customers/api/deals/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/route.js +20 -1
- package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
- package/dist/modules/customers/api/pipeline-stages/reorder/route.js +69 -0
- package/dist/modules/customers/api/pipeline-stages/reorder/route.js.map +7 -0
- package/dist/modules/customers/api/pipeline-stages/route.js +275 -0
- package/dist/modules/customers/api/pipeline-stages/route.js.map +7 -0
- package/dist/modules/customers/api/pipelines/route.js +245 -0
- package/dist/modules/customers/api/pipelines/route.js.map +7 -0
- package/dist/modules/customers/backend/config/customers/page.js +2 -0
- package/dist/modules/customers/backend/config/customers/page.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +439 -0
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +7 -0
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js +17 -0
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js.map +7 -0
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +19 -1
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +35 -1
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js +102 -74
- package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
- package/dist/modules/customers/cli.js +28 -2
- package/dist/modules/customers/cli.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +34 -2
- package/dist/modules/customers/commands/deals.js.map +2 -2
- package/dist/modules/customers/commands/index.js +2 -0
- package/dist/modules/customers/commands/index.js.map +2 -2
- package/dist/modules/customers/commands/pipeline-stages.js +126 -0
- package/dist/modules/customers/commands/pipeline-stages.js.map +7 -0
- package/dist/modules/customers/commands/pipelines.js +87 -0
- package/dist/modules/customers/commands/pipelines.js.map +7 -0
- package/dist/modules/customers/components/DictionarySettings.js +0 -5
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/PipelineSettings.js +474 -0
- package/dist/modules/customers/components/PipelineSettings.js.map +7 -0
- package/dist/modules/customers/components/detail/DealForm.js +84 -12
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/data/entities.js +78 -0
- package/dist/modules/customers/data/entities.js.map +2 -2
- package/dist/modules/customers/data/validators.js +44 -0
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/migrations/Migration20260218191730.js +77 -0
- package/dist/modules/customers/migrations/Migration20260218191730.js.map +7 -0
- package/dist/modules/customers/setup.js +7 -3
- package/dist/modules/customers/setup.js.map +2 -2
- package/dist/modules/workflows/migrations/Migration20260222205305.js +14 -0
- package/dist/modules/workflows/migrations/Migration20260222205305.js.map +7 -0
- package/generated/entities/customer_deal/index.ts +2 -0
- package/generated/entities/customer_pipeline/index.ts +7 -0
- package/generated/entities/customer_pipeline_stage/index.ts +8 -0
- package/generated/entities.ids.generated.ts +2 -0
- package/generated/entity-fields-registry.ts +4 -0
- package/package.json +2 -2
- package/src/modules/customers/acl.ts +2 -0
- package/src/modules/customers/api/deals/[id]/route.ts +4 -0
- package/src/modules/customers/api/deals/route.ts +12 -0
- package/src/modules/customers/api/dictionaries/[kind]/route.ts +21 -1
- package/src/modules/customers/api/pipeline-stages/reorder/route.ts +71 -0
- package/src/modules/customers/api/pipeline-stages/route.ts +296 -0
- package/src/modules/customers/api/pipelines/route.ts +261 -0
- package/src/modules/customers/backend/config/customers/page.tsx +2 -0
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.meta.ts +13 -0
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +512 -0
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +21 -1
- package/src/modules/customers/backend/customers/deals/page.tsx +33 -1
- package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +119 -79
- package/src/modules/customers/cli.ts +29 -1
- package/src/modules/customers/commands/deals.ts +44 -1
- package/src/modules/customers/commands/index.ts +2 -0
- package/src/modules/customers/commands/pipeline-stages.ts +156 -0
- package/src/modules/customers/commands/pipelines.ts +105 -0
- package/src/modules/customers/components/DictionarySettings.tsx +0 -5
- package/src/modules/customers/components/PipelineSettings.tsx +570 -0
- package/src/modules/customers/components/detail/DealForm.tsx +89 -11
- package/src/modules/customers/data/entities.ts +64 -0
- package/src/modules/customers/data/validators.ts +57 -0
- package/src/modules/customers/i18n/de.json +4 -0
- package/src/modules/customers/i18n/en.json +4 -0
- package/src/modules/customers/i18n/es.json +4 -0
- package/src/modules/customers/i18n/pl.json +5 -1
- package/src/modules/customers/migrations/Migration20260218191730.ts +84 -0
- package/src/modules/customers/setup.ts +5 -1
- package/src/modules/workflows/migrations/Migration20260222205305.ts +13 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Page, PageBody } from "@open-mercato/ui/backend/Page";
|
|
5
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
6
|
+
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
7
|
+
import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
8
|
+
import { Button } from "@open-mercato/ui/primitives/button";
|
|
9
|
+
import { Input } from "@open-mercato/ui/primitives/input";
|
|
10
|
+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@open-mercato/ui/primitives/dialog";
|
|
11
|
+
import { Spinner } from "@open-mercato/ui/primitives/spinner";
|
|
12
|
+
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
13
|
+
import { AppearanceSelector } from "@open-mercato/core/modules/dictionaries/components/AppearanceSelector";
|
|
14
|
+
import { renderDictionaryColor, renderDictionaryIcon, ICON_SUGGESTIONS } from "@open-mercato/core/modules/dictionaries/components/dictionaryAppearance";
|
|
15
|
+
function PipelineStagesPage() {
|
|
16
|
+
const t = useT();
|
|
17
|
+
const { confirm, ConfirmDialogElement } = useConfirmDialog();
|
|
18
|
+
const [pipelines, setPipelines] = React.useState([]);
|
|
19
|
+
const [selectedPipelineId, setSelectedPipelineId] = React.useState(null);
|
|
20
|
+
const [stages, setStages] = React.useState([]);
|
|
21
|
+
const [loadingPipelines, setLoadingPipelines] = React.useState(true);
|
|
22
|
+
const [loadingStages, setLoadingStages] = React.useState(false);
|
|
23
|
+
const [pipelineDialog, setPipelineDialog] = React.useState(null);
|
|
24
|
+
const [stageDialog, setStageDialog] = React.useState(null);
|
|
25
|
+
const [pipelineName, setPipelineName] = React.useState("");
|
|
26
|
+
const [pipelineIsDefault, setPipelineIsDefault] = React.useState(false);
|
|
27
|
+
const [stageName, setStageName] = React.useState("");
|
|
28
|
+
const [stageColor, setStageColor] = React.useState(null);
|
|
29
|
+
const [stageIcon, setStageIcon] = React.useState(null);
|
|
30
|
+
const [saving, setSaving] = React.useState(false);
|
|
31
|
+
const selectedPipeline = React.useMemo(
|
|
32
|
+
() => pipelines.find((p) => p.id === selectedPipelineId) ?? null,
|
|
33
|
+
[pipelines, selectedPipelineId]
|
|
34
|
+
);
|
|
35
|
+
const loadPipelines = React.useCallback(async () => {
|
|
36
|
+
setLoadingPipelines(true);
|
|
37
|
+
try {
|
|
38
|
+
const result = await apiCall("/api/customers/pipelines");
|
|
39
|
+
if (result.ok && result.result?.items) {
|
|
40
|
+
const items = result.result.items;
|
|
41
|
+
setPipelines(items);
|
|
42
|
+
if (!selectedPipelineId && items.length > 0) {
|
|
43
|
+
const defaultPipeline = items.find((p) => p.isDefault) ?? items[0];
|
|
44
|
+
setSelectedPipelineId(defaultPipeline.id);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
flash(t("customers.config.pipelineStages.errorLoadPipelines", "Failed to load pipelines"), "error");
|
|
49
|
+
} finally {
|
|
50
|
+
setLoadingPipelines(false);
|
|
51
|
+
}
|
|
52
|
+
}, [selectedPipelineId, t]);
|
|
53
|
+
const loadStages = React.useCallback(async (pipelineId) => {
|
|
54
|
+
setLoadingStages(true);
|
|
55
|
+
try {
|
|
56
|
+
const result = await apiCall(
|
|
57
|
+
`/api/customers/pipeline-stages?pipelineId=${encodeURIComponent(pipelineId)}`
|
|
58
|
+
);
|
|
59
|
+
if (result.ok && result.result?.items) {
|
|
60
|
+
setStages(result.result.items);
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
flash(t("customers.config.pipelineStages.errorLoadStages", "Failed to load pipeline stages"), "error");
|
|
64
|
+
} finally {
|
|
65
|
+
setLoadingStages(false);
|
|
66
|
+
}
|
|
67
|
+
}, [t]);
|
|
68
|
+
React.useEffect(() => {
|
|
69
|
+
void loadPipelines();
|
|
70
|
+
}, [loadPipelines]);
|
|
71
|
+
React.useEffect(() => {
|
|
72
|
+
if (selectedPipelineId) {
|
|
73
|
+
void loadStages(selectedPipelineId);
|
|
74
|
+
} else {
|
|
75
|
+
setStages([]);
|
|
76
|
+
}
|
|
77
|
+
}, [selectedPipelineId, loadStages]);
|
|
78
|
+
function openCreatePipeline() {
|
|
79
|
+
setPipelineName("");
|
|
80
|
+
setPipelineIsDefault(false);
|
|
81
|
+
setPipelineDialog({ mode: "create" });
|
|
82
|
+
}
|
|
83
|
+
function openEditPipeline(pipeline) {
|
|
84
|
+
setPipelineName(pipeline.name);
|
|
85
|
+
setPipelineIsDefault(pipeline.isDefault);
|
|
86
|
+
setPipelineDialog({ mode: "edit", pipeline });
|
|
87
|
+
}
|
|
88
|
+
async function savePipeline() {
|
|
89
|
+
if (!pipelineName.trim()) return;
|
|
90
|
+
setSaving(true);
|
|
91
|
+
try {
|
|
92
|
+
if (pipelineDialog?.mode === "create") {
|
|
93
|
+
const result = await apiCall("/api/customers/pipelines", {
|
|
94
|
+
method: "POST",
|
|
95
|
+
body: JSON.stringify({ name: pipelineName.trim(), isDefault: pipelineIsDefault }),
|
|
96
|
+
headers: { "Content-Type": "application/json" }
|
|
97
|
+
});
|
|
98
|
+
if (!result.ok) {
|
|
99
|
+
flash(t("customers.config.pipelineStages.errorCreatePipeline", "Failed to create pipeline"), "error");
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
flash(t("customers.config.pipelineStages.createdPipeline", "Pipeline created"), "success");
|
|
103
|
+
const newId = result.result?.id ?? null;
|
|
104
|
+
await loadPipelines();
|
|
105
|
+
if (newId) setSelectedPipelineId(newId);
|
|
106
|
+
} else if (pipelineDialog?.mode === "edit") {
|
|
107
|
+
const result = await apiCall("/api/customers/pipelines", {
|
|
108
|
+
method: "PUT",
|
|
109
|
+
body: JSON.stringify({ id: pipelineDialog.pipeline.id, name: pipelineName.trim(), isDefault: pipelineIsDefault }),
|
|
110
|
+
headers: { "Content-Type": "application/json" }
|
|
111
|
+
});
|
|
112
|
+
if (!result.ok) {
|
|
113
|
+
flash(t("customers.config.pipelineStages.errorUpdatePipeline", "Failed to update pipeline"), "error");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
flash(t("customers.config.pipelineStages.updatedPipeline", "Pipeline updated"), "success");
|
|
117
|
+
await loadPipelines();
|
|
118
|
+
}
|
|
119
|
+
setPipelineDialog(null);
|
|
120
|
+
} finally {
|
|
121
|
+
setSaving(false);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function deletePipeline(pipeline) {
|
|
125
|
+
const confirmed = await confirm({
|
|
126
|
+
title: t("customers.config.pipelineStages.deletePipelineTitle", "Delete pipeline?"),
|
|
127
|
+
text: t(
|
|
128
|
+
"customers.config.pipelineStages.deletePipelineDesc",
|
|
129
|
+
"This pipeline and all its stages will be permanently removed. Deals assigned to it will lose their pipeline assignment."
|
|
130
|
+
),
|
|
131
|
+
confirmText: t("customers.config.pipelineStages.deletePipelineConfirm", "Delete"),
|
|
132
|
+
variant: "destructive"
|
|
133
|
+
});
|
|
134
|
+
if (!confirmed) return;
|
|
135
|
+
const result = await apiCall("/api/customers/pipelines", {
|
|
136
|
+
method: "DELETE",
|
|
137
|
+
body: JSON.stringify({ id: pipeline.id }),
|
|
138
|
+
headers: { "Content-Type": "application/json" }
|
|
139
|
+
});
|
|
140
|
+
if (!result.ok) {
|
|
141
|
+
const error = result.result?.error;
|
|
142
|
+
flash(error ?? t("customers.config.pipelineStages.errorDeletePipeline", "Failed to delete pipeline"), "error");
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
flash(t("customers.config.pipelineStages.deletedPipeline", "Pipeline deleted"), "success");
|
|
146
|
+
if (selectedPipelineId === pipeline.id) setSelectedPipelineId(null);
|
|
147
|
+
await loadPipelines();
|
|
148
|
+
}
|
|
149
|
+
function openCreateStage() {
|
|
150
|
+
setStageName("");
|
|
151
|
+
setStageColor(null);
|
|
152
|
+
setStageIcon(null);
|
|
153
|
+
setStageDialog({ mode: "create" });
|
|
154
|
+
}
|
|
155
|
+
function openEditStage(stage) {
|
|
156
|
+
setStageName(stage.label);
|
|
157
|
+
setStageColor(stage.color);
|
|
158
|
+
setStageIcon(stage.icon);
|
|
159
|
+
setStageDialog({ mode: "edit", stage });
|
|
160
|
+
}
|
|
161
|
+
async function saveStage() {
|
|
162
|
+
if (!stageName.trim() || !selectedPipelineId) return;
|
|
163
|
+
setSaving(true);
|
|
164
|
+
try {
|
|
165
|
+
if (stageDialog?.mode === "create") {
|
|
166
|
+
const result = await apiCall("/api/customers/pipeline-stages", {
|
|
167
|
+
method: "POST",
|
|
168
|
+
body: JSON.stringify({ pipelineId: selectedPipelineId, label: stageName.trim(), color: stageColor, icon: stageIcon }),
|
|
169
|
+
headers: { "Content-Type": "application/json" }
|
|
170
|
+
});
|
|
171
|
+
if (!result.ok) {
|
|
172
|
+
flash(t("customers.config.pipelineStages.errorCreateStage", "Failed to create stage"), "error");
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
flash(t("customers.config.pipelineStages.createdStage", "Stage created"), "success");
|
|
176
|
+
} else if (stageDialog?.mode === "edit") {
|
|
177
|
+
const result = await apiCall("/api/customers/pipeline-stages", {
|
|
178
|
+
method: "PUT",
|
|
179
|
+
body: JSON.stringify({ id: stageDialog.stage.id, label: stageName.trim(), color: stageColor, icon: stageIcon }),
|
|
180
|
+
headers: { "Content-Type": "application/json" }
|
|
181
|
+
});
|
|
182
|
+
if (!result.ok) {
|
|
183
|
+
flash(t("customers.config.pipelineStages.errorUpdateStage", "Failed to update stage"), "error");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
flash(t("customers.config.pipelineStages.updatedStage", "Stage updated"), "success");
|
|
187
|
+
}
|
|
188
|
+
setStageDialog(null);
|
|
189
|
+
await loadStages(selectedPipelineId);
|
|
190
|
+
} finally {
|
|
191
|
+
setSaving(false);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function deleteStage(stage) {
|
|
195
|
+
const confirmed = await confirm({
|
|
196
|
+
title: t("customers.config.pipelineStages.deleteStagTitle", "Delete stage?"),
|
|
197
|
+
text: t(
|
|
198
|
+
"customers.config.pipelineStages.deleteStageDesc",
|
|
199
|
+
"This stage will be permanently removed. Deals assigned to it will lose their stage assignment."
|
|
200
|
+
),
|
|
201
|
+
confirmText: t("customers.config.pipelineStages.deleteStageConfirm", "Delete"),
|
|
202
|
+
variant: "destructive"
|
|
203
|
+
});
|
|
204
|
+
if (!confirmed) return;
|
|
205
|
+
const result = await apiCall("/api/customers/pipeline-stages", {
|
|
206
|
+
method: "DELETE",
|
|
207
|
+
body: JSON.stringify({ id: stage.id }),
|
|
208
|
+
headers: { "Content-Type": "application/json" }
|
|
209
|
+
});
|
|
210
|
+
if (!result.ok) {
|
|
211
|
+
const error = result.result?.error;
|
|
212
|
+
flash(error ?? t("customers.config.pipelineStages.errorDeleteStage", "Failed to delete stage"), "error");
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
flash(t("customers.config.pipelineStages.deletedStage", "Stage deleted"), "success");
|
|
216
|
+
if (selectedPipelineId) await loadStages(selectedPipelineId);
|
|
217
|
+
}
|
|
218
|
+
async function moveStage(index, direction) {
|
|
219
|
+
const nextIndex = direction === "up" ? index - 1 : index + 1;
|
|
220
|
+
if (nextIndex < 0 || nextIndex >= stages.length) return;
|
|
221
|
+
const reordered = [...stages];
|
|
222
|
+
const [moved] = reordered.splice(index, 1);
|
|
223
|
+
reordered.splice(nextIndex, 0, moved);
|
|
224
|
+
const updated = reordered.map((stage, i) => ({ ...stage, order: i }));
|
|
225
|
+
setStages(updated);
|
|
226
|
+
const result = await apiCall("/api/customers/pipeline-stages/reorder", {
|
|
227
|
+
method: "POST",
|
|
228
|
+
body: JSON.stringify({ stages: updated.map((s) => ({ id: s.id, order: s.order })) }),
|
|
229
|
+
headers: { "Content-Type": "application/json" }
|
|
230
|
+
});
|
|
231
|
+
if (!result.ok) {
|
|
232
|
+
flash(t("customers.config.pipelineStages.errorReorder", "Failed to reorder stages"), "error");
|
|
233
|
+
if (selectedPipelineId) await loadStages(selectedPipelineId);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const appearanceLabels = React.useMemo(() => ({
|
|
237
|
+
colorLabel: t("customers.config.pipelineStages.colorLabel", "Color"),
|
|
238
|
+
colorHelp: t("customers.config.pipelineStages.colorHelp", "Pick a highlight color for this entry."),
|
|
239
|
+
colorClearLabel: t("customers.config.pipelineStages.colorClear", "Remove color"),
|
|
240
|
+
iconLabel: t("customers.config.pipelineStages.iconLabel", "Icon"),
|
|
241
|
+
iconPlaceholder: t("customers.config.pipelineStages.iconPlaceholder", "Type an emoji or pick one of the suggestions."),
|
|
242
|
+
iconPickerTriggerLabel: t("customers.config.pipelineStages.iconBrowse", "Browse icons and emojis"),
|
|
243
|
+
iconSearchPlaceholder: t("customers.config.pipelineStages.iconSearchPlaceholder", "Search icons or emojis\u2026"),
|
|
244
|
+
iconSearchEmptyLabel: t("customers.config.pipelineStages.iconSearchEmpty", "No icons match your search."),
|
|
245
|
+
iconSuggestionsLabel: t("customers.config.pipelineStages.iconSuggestions", "Suggestions"),
|
|
246
|
+
iconClearLabel: t("customers.config.pipelineStages.iconClear", "Remove icon"),
|
|
247
|
+
previewEmptyLabel: t("customers.config.pipelineStages.previewEmpty", "None")
|
|
248
|
+
}), [t]);
|
|
249
|
+
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsxs(PageBody, { children: [
|
|
250
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-6 max-w-2xl", children: [
|
|
251
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
252
|
+
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: t("customers.config.pipelineStages.title", "Pipeline stages") }),
|
|
253
|
+
/* @__PURE__ */ jsxs(
|
|
254
|
+
"a",
|
|
255
|
+
{
|
|
256
|
+
href: "/backend/customers/deals/pipeline",
|
|
257
|
+
className: "text-sm text-muted-foreground hover:underline",
|
|
258
|
+
children: [
|
|
259
|
+
t("customers.config.pipelineStages.viewBoard", "View pipeline board"),
|
|
260
|
+
" \u2192"
|
|
261
|
+
]
|
|
262
|
+
}
|
|
263
|
+
)
|
|
264
|
+
] }),
|
|
265
|
+
loadingPipelines ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
266
|
+
/* @__PURE__ */ jsx(Spinner, { size: "sm" }),
|
|
267
|
+
t("customers.config.pipelineStages.loadingPipelines", "Loading pipelines\u2026")
|
|
268
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
269
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
270
|
+
/* @__PURE__ */ jsxs(
|
|
271
|
+
"select",
|
|
272
|
+
{
|
|
273
|
+
className: "flex h-9 w-full max-w-xs rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus:outline-none focus:ring-1 focus:ring-ring",
|
|
274
|
+
value: selectedPipelineId ?? "",
|
|
275
|
+
onChange: (e) => setSelectedPipelineId(e.target.value || null),
|
|
276
|
+
children: [
|
|
277
|
+
pipelines.length === 0 && /* @__PURE__ */ jsx("option", { value: "", children: t("customers.config.pipelineStages.noPipelines", "No pipelines yet") }),
|
|
278
|
+
pipelines.map((p) => /* @__PURE__ */ jsxs("option", { value: p.id, children: [
|
|
279
|
+
p.name,
|
|
280
|
+
p.isDefault ? ` (${t("customers.config.pipelineStages.default", "default")})` : ""
|
|
281
|
+
] }, p.id))
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
),
|
|
285
|
+
selectedPipeline && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
286
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => openEditPipeline(selectedPipeline), children: t("customers.config.pipelineStages.editPipeline", "Edit") }),
|
|
287
|
+
/* @__PURE__ */ jsx(
|
|
288
|
+
Button,
|
|
289
|
+
{
|
|
290
|
+
variant: "outline",
|
|
291
|
+
size: "sm",
|
|
292
|
+
className: "text-destructive",
|
|
293
|
+
onClick: () => deletePipeline(selectedPipeline),
|
|
294
|
+
children: t("customers.config.pipelineStages.deletePipeline", "Delete")
|
|
295
|
+
}
|
|
296
|
+
)
|
|
297
|
+
] }),
|
|
298
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: openCreatePipeline, children: t("customers.config.pipelineStages.addPipeline", "+ Add pipeline") })
|
|
299
|
+
] }),
|
|
300
|
+
selectedPipelineId && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
301
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
302
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-muted-foreground", children: t("customers.config.pipelineStages.stagesTitle", "Stages") }),
|
|
303
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", onClick: openCreateStage, children: t("customers.config.pipelineStages.addStage", "+ Add stage") })
|
|
304
|
+
] }),
|
|
305
|
+
loadingStages ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
306
|
+
/* @__PURE__ */ jsx(Spinner, { size: "sm" }),
|
|
307
|
+
t("customers.config.pipelineStages.loadingStages", "Loading stages\u2026")
|
|
308
|
+
] }) : stages.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("customers.config.pipelineStages.noStages", "No stages yet. Add your first stage.") }) : /* @__PURE__ */ jsx("div", { className: "divide-y rounded-md border", children: stages.map((stage, index) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [
|
|
309
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
310
|
+
/* @__PURE__ */ jsx(
|
|
311
|
+
"button",
|
|
312
|
+
{
|
|
313
|
+
type: "button",
|
|
314
|
+
className: "text-muted-foreground hover:text-foreground disabled:opacity-30",
|
|
315
|
+
onClick: () => moveStage(index, "up"),
|
|
316
|
+
disabled: index === 0,
|
|
317
|
+
"aria-label": t("customers.config.pipelineStages.moveUp", "Move up"),
|
|
318
|
+
children: "\u2191"
|
|
319
|
+
}
|
|
320
|
+
),
|
|
321
|
+
/* @__PURE__ */ jsx(
|
|
322
|
+
"button",
|
|
323
|
+
{
|
|
324
|
+
type: "button",
|
|
325
|
+
className: "text-muted-foreground hover:text-foreground disabled:opacity-30",
|
|
326
|
+
onClick: () => moveStage(index, "down"),
|
|
327
|
+
disabled: index === stages.length - 1,
|
|
328
|
+
"aria-label": t("customers.config.pipelineStages.moveDown", "Move down"),
|
|
329
|
+
children: "\u2193"
|
|
330
|
+
}
|
|
331
|
+
)
|
|
332
|
+
] }),
|
|
333
|
+
/* @__PURE__ */ jsxs("span", { className: "flex-1 text-sm flex items-center gap-2", children: [
|
|
334
|
+
stage.color ? renderDictionaryColor(stage.color) : null,
|
|
335
|
+
stage.icon ? renderDictionaryIcon(stage.icon) : null,
|
|
336
|
+
stage.label
|
|
337
|
+
] }),
|
|
338
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => openEditStage(stage), children: t("customers.config.pipelineStages.editStage", "Edit") }),
|
|
339
|
+
/* @__PURE__ */ jsx(
|
|
340
|
+
Button,
|
|
341
|
+
{
|
|
342
|
+
variant: "ghost",
|
|
343
|
+
size: "sm",
|
|
344
|
+
className: "text-destructive",
|
|
345
|
+
onClick: () => deleteStage(stage),
|
|
346
|
+
children: t("customers.config.pipelineStages.deleteStage", "Delete")
|
|
347
|
+
}
|
|
348
|
+
)
|
|
349
|
+
] }, stage.id)) })
|
|
350
|
+
] })
|
|
351
|
+
] })
|
|
352
|
+
] }),
|
|
353
|
+
/* @__PURE__ */ jsx(Dialog, { open: pipelineDialog !== null, onOpenChange: (open) => {
|
|
354
|
+
if (!open) setPipelineDialog(null);
|
|
355
|
+
}, children: /* @__PURE__ */ jsxs(DialogContent, { children: [
|
|
356
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: pipelineDialog?.mode === "create" ? t("customers.config.pipelineStages.createPipelineTitle", "Create pipeline") : t("customers.config.pipelineStages.editPipelineTitle", "Edit pipeline") }) }),
|
|
357
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4 py-2", children: [
|
|
358
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
359
|
+
/* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: t("customers.config.pipelineStages.pipelineName", "Name") }),
|
|
360
|
+
/* @__PURE__ */ jsx(
|
|
361
|
+
Input,
|
|
362
|
+
{
|
|
363
|
+
value: pipelineName,
|
|
364
|
+
onChange: (e) => setPipelineName(e.target.value),
|
|
365
|
+
placeholder: t("customers.config.pipelineStages.pipelineNamePlaceholder", "e.g. Sales Pipeline"),
|
|
366
|
+
onKeyDown: (e) => {
|
|
367
|
+
if (e.key === "Enter") {
|
|
368
|
+
e.preventDefault();
|
|
369
|
+
void savePipeline();
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
autoFocus: true
|
|
373
|
+
}
|
|
374
|
+
)
|
|
375
|
+
] }),
|
|
376
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm cursor-pointer", children: [
|
|
377
|
+
/* @__PURE__ */ jsx(
|
|
378
|
+
"input",
|
|
379
|
+
{
|
|
380
|
+
type: "checkbox",
|
|
381
|
+
checked: pipelineIsDefault,
|
|
382
|
+
onChange: (e) => setPipelineIsDefault(e.target.checked)
|
|
383
|
+
}
|
|
384
|
+
),
|
|
385
|
+
t("customers.config.pipelineStages.setAsDefault", "Set as default pipeline")
|
|
386
|
+
] })
|
|
387
|
+
] }),
|
|
388
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
389
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => setPipelineDialog(null), disabled: saving, children: t("customers.config.pipelineStages.cancel", "Cancel") }),
|
|
390
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => void savePipeline(), disabled: saving || !pipelineName.trim(), children: saving ? /* @__PURE__ */ jsx(Spinner, { size: "sm" }) : t("customers.config.pipelineStages.save", "Save") })
|
|
391
|
+
] })
|
|
392
|
+
] }) }),
|
|
393
|
+
/* @__PURE__ */ jsx(Dialog, { open: stageDialog !== null, onOpenChange: (open) => {
|
|
394
|
+
if (!open) setStageDialog(null);
|
|
395
|
+
}, children: /* @__PURE__ */ jsxs(DialogContent, { children: [
|
|
396
|
+
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, { children: stageDialog?.mode === "create" ? t("customers.config.pipelineStages.createStageTitle", "Create stage") : t("customers.config.pipelineStages.editStageTitle", "Edit stage") }) }),
|
|
397
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-4 py-2", children: [
|
|
398
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
399
|
+
/* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: t("customers.config.pipelineStages.stageName", "Stage name") }),
|
|
400
|
+
/* @__PURE__ */ jsx(
|
|
401
|
+
Input,
|
|
402
|
+
{
|
|
403
|
+
value: stageName,
|
|
404
|
+
onChange: (e) => setStageName(e.target.value),
|
|
405
|
+
placeholder: t("customers.config.pipelineStages.stageNamePlaceholder", "e.g. Qualification"),
|
|
406
|
+
onKeyDown: (e) => {
|
|
407
|
+
if (e.key === "Enter") {
|
|
408
|
+
e.preventDefault();
|
|
409
|
+
void saveStage();
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
autoFocus: true
|
|
413
|
+
}
|
|
414
|
+
)
|
|
415
|
+
] }),
|
|
416
|
+
/* @__PURE__ */ jsx(
|
|
417
|
+
AppearanceSelector,
|
|
418
|
+
{
|
|
419
|
+
color: stageColor,
|
|
420
|
+
icon: stageIcon,
|
|
421
|
+
onColorChange: setStageColor,
|
|
422
|
+
onIconChange: setStageIcon,
|
|
423
|
+
labels: appearanceLabels,
|
|
424
|
+
iconSuggestions: ICON_SUGGESTIONS
|
|
425
|
+
}
|
|
426
|
+
)
|
|
427
|
+
] }),
|
|
428
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
429
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: () => setStageDialog(null), disabled: saving, children: t("customers.config.pipelineStages.cancel", "Cancel") }),
|
|
430
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => void saveStage(), disabled: saving || !stageName.trim(), children: saving ? /* @__PURE__ */ jsx(Spinner, { size: "sm" }) : t("customers.config.pipelineStages.save", "Save") })
|
|
431
|
+
] })
|
|
432
|
+
] }) }),
|
|
433
|
+
ConfirmDialogElement
|
|
434
|
+
] }) });
|
|
435
|
+
}
|
|
436
|
+
export {
|
|
437
|
+
PipelineStagesPage as default
|
|
438
|
+
};
|
|
439
|
+
//# sourceMappingURL=page.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../src/modules/customers/backend/config/customers/pipeline-stages/page.tsx"],
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@open-mercato/ui/primitives/dialog'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { AppearanceSelector, type AppearanceSelectorLabels } from '@open-mercato/core/modules/dictionaries/components/AppearanceSelector'\nimport { renderDictionaryColor, renderDictionaryIcon, ICON_SUGGESTIONS } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'\n\ntype Pipeline = {\n id: string\n name: string\n isDefault: boolean\n}\n\ntype PipelineStage = {\n id: string\n pipelineId: string\n label: string\n order: number\n color: string | null\n icon: string | null\n}\n\ntype PipelineDialogState =\n | { mode: 'create' }\n | { mode: 'edit'; pipeline: Pipeline }\n | null\n\ntype StageDialogState =\n | { mode: 'create' }\n | { mode: 'edit'; stage: PipelineStage }\n | null\n\nexport default function PipelineStagesPage() {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const [pipelines, setPipelines] = React.useState<Pipeline[]>([])\n const [selectedPipelineId, setSelectedPipelineId] = React.useState<string | null>(null)\n const [stages, setStages] = React.useState<PipelineStage[]>([])\n const [loadingPipelines, setLoadingPipelines] = React.useState(true)\n const [loadingStages, setLoadingStages] = React.useState(false)\n const [pipelineDialog, setPipelineDialog] = React.useState<PipelineDialogState>(null)\n const [stageDialog, setStageDialog] = React.useState<StageDialogState>(null)\n const [pipelineName, setPipelineName] = React.useState('')\n const [pipelineIsDefault, setPipelineIsDefault] = React.useState(false)\n const [stageName, setStageName] = React.useState('')\n const [stageColor, setStageColor] = React.useState<string | null>(null)\n const [stageIcon, setStageIcon] = React.useState<string | null>(null)\n const [saving, setSaving] = React.useState(false)\n\n const selectedPipeline = React.useMemo(\n () => pipelines.find((p) => p.id === selectedPipelineId) ?? null,\n [pipelines, selectedPipelineId],\n )\n\n const loadPipelines = React.useCallback(async () => {\n setLoadingPipelines(true)\n try {\n const result = await apiCall<{ items: Pipeline[] }>('/api/customers/pipelines')\n if (result.ok && result.result?.items) {\n const items = result.result.items\n setPipelines(items)\n if (!selectedPipelineId && items.length > 0) {\n const defaultPipeline = items.find((p) => p.isDefault) ?? items[0]\n setSelectedPipelineId(defaultPipeline.id)\n }\n }\n } catch {\n flash(t('customers.config.pipelineStages.errorLoadPipelines', 'Failed to load pipelines'), 'error')\n } finally {\n setLoadingPipelines(false)\n }\n }, [selectedPipelineId, t])\n\n const loadStages = React.useCallback(async (pipelineId: string) => {\n setLoadingStages(true)\n try {\n const result = await apiCall<{ items: PipelineStage[] }>(\n `/api/customers/pipeline-stages?pipelineId=${encodeURIComponent(pipelineId)}`\n )\n if (result.ok && result.result?.items) {\n setStages(result.result.items)\n }\n } catch {\n flash(t('customers.config.pipelineStages.errorLoadStages', 'Failed to load pipeline stages'), 'error')\n } finally {\n setLoadingStages(false)\n }\n }, [t])\n\n React.useEffect(() => {\n void loadPipelines()\n }, [loadPipelines])\n\n React.useEffect(() => {\n if (selectedPipelineId) {\n void loadStages(selectedPipelineId)\n } else {\n setStages([])\n }\n }, [selectedPipelineId, loadStages])\n\n function openCreatePipeline() {\n setPipelineName('')\n setPipelineIsDefault(false)\n setPipelineDialog({ mode: 'create' })\n }\n\n function openEditPipeline(pipeline: Pipeline) {\n setPipelineName(pipeline.name)\n setPipelineIsDefault(pipeline.isDefault)\n setPipelineDialog({ mode: 'edit', pipeline })\n }\n\n async function savePipeline() {\n if (!pipelineName.trim()) return\n setSaving(true)\n try {\n if (pipelineDialog?.mode === 'create') {\n const result = await apiCall<{ id: string }>('/api/customers/pipelines', {\n method: 'POST',\n body: JSON.stringify({ name: pipelineName.trim(), isDefault: pipelineIsDefault }),\n headers: { 'Content-Type': 'application/json' },\n })\n if (!result.ok) {\n flash(t('customers.config.pipelineStages.errorCreatePipeline', 'Failed to create pipeline'), 'error')\n return\n }\n flash(t('customers.config.pipelineStages.createdPipeline', 'Pipeline created'), 'success')\n const newId = result.result?.id ?? null\n await loadPipelines()\n if (newId) setSelectedPipelineId(newId)\n } else if (pipelineDialog?.mode === 'edit') {\n const result = await apiCall('/api/customers/pipelines', {\n method: 'PUT',\n body: JSON.stringify({ id: pipelineDialog.pipeline.id, name: pipelineName.trim(), isDefault: pipelineIsDefault }),\n headers: { 'Content-Type': 'application/json' },\n })\n if (!result.ok) {\n flash(t('customers.config.pipelineStages.errorUpdatePipeline', 'Failed to update pipeline'), 'error')\n return\n }\n flash(t('customers.config.pipelineStages.updatedPipeline', 'Pipeline updated'), 'success')\n await loadPipelines()\n }\n setPipelineDialog(null)\n } finally {\n setSaving(false)\n }\n }\n\n async function deletePipeline(pipeline: Pipeline) {\n const confirmed = await confirm({\n title: t('customers.config.pipelineStages.deletePipelineTitle', 'Delete pipeline?'),\n text: t(\n 'customers.config.pipelineStages.deletePipelineDesc',\n 'This pipeline and all its stages will be permanently removed. Deals assigned to it will lose their pipeline assignment.',\n ),\n confirmText: t('customers.config.pipelineStages.deletePipelineConfirm', 'Delete'),\n variant: 'destructive',\n })\n if (!confirmed) return\n const result = await apiCall('/api/customers/pipelines', {\n method: 'DELETE',\n body: JSON.stringify({ id: pipeline.id }),\n headers: { 'Content-Type': 'application/json' },\n })\n if (!result.ok) {\n const error = (result.result as { error?: string })?.error\n flash(error ?? t('customers.config.pipelineStages.errorDeletePipeline', 'Failed to delete pipeline'), 'error')\n return\n }\n flash(t('customers.config.pipelineStages.deletedPipeline', 'Pipeline deleted'), 'success')\n if (selectedPipelineId === pipeline.id) setSelectedPipelineId(null)\n await loadPipelines()\n }\n\n function openCreateStage() {\n setStageName('')\n setStageColor(null)\n setStageIcon(null)\n setStageDialog({ mode: 'create' })\n }\n\n function openEditStage(stage: PipelineStage) {\n setStageName(stage.label)\n setStageColor(stage.color)\n setStageIcon(stage.icon)\n setStageDialog({ mode: 'edit', stage })\n }\n\n async function saveStage() {\n if (!stageName.trim() || !selectedPipelineId) return\n setSaving(true)\n try {\n if (stageDialog?.mode === 'create') {\n const result = await apiCall('/api/customers/pipeline-stages', {\n method: 'POST',\n body: JSON.stringify({ pipelineId: selectedPipelineId, label: stageName.trim(), color: stageColor, icon: stageIcon }),\n headers: { 'Content-Type': 'application/json' },\n })\n if (!result.ok) {\n flash(t('customers.config.pipelineStages.errorCreateStage', 'Failed to create stage'), 'error')\n return\n }\n flash(t('customers.config.pipelineStages.createdStage', 'Stage created'), 'success')\n } else if (stageDialog?.mode === 'edit') {\n const result = await apiCall('/api/customers/pipeline-stages', {\n method: 'PUT',\n body: JSON.stringify({ id: stageDialog.stage.id, label: stageName.trim(), color: stageColor, icon: stageIcon }),\n headers: { 'Content-Type': 'application/json' },\n })\n if (!result.ok) {\n flash(t('customers.config.pipelineStages.errorUpdateStage', 'Failed to update stage'), 'error')\n return\n }\n flash(t('customers.config.pipelineStages.updatedStage', 'Stage updated'), 'success')\n }\n setStageDialog(null)\n await loadStages(selectedPipelineId)\n } finally {\n setSaving(false)\n }\n }\n\n async function deleteStage(stage: PipelineStage) {\n const confirmed = await confirm({\n title: t('customers.config.pipelineStages.deleteStagTitle', 'Delete stage?'),\n text: t(\n 'customers.config.pipelineStages.deleteStageDesc',\n 'This stage will be permanently removed. Deals assigned to it will lose their stage assignment.',\n ),\n confirmText: t('customers.config.pipelineStages.deleteStageConfirm', 'Delete'),\n variant: 'destructive',\n })\n if (!confirmed) return\n const result = await apiCall('/api/customers/pipeline-stages', {\n method: 'DELETE',\n body: JSON.stringify({ id: stage.id }),\n headers: { 'Content-Type': 'application/json' },\n })\n if (!result.ok) {\n const error = (result.result as { error?: string })?.error\n flash(error ?? t('customers.config.pipelineStages.errorDeleteStage', 'Failed to delete stage'), 'error')\n return\n }\n flash(t('customers.config.pipelineStages.deletedStage', 'Stage deleted'), 'success')\n if (selectedPipelineId) await loadStages(selectedPipelineId)\n }\n\n async function moveStage(index: number, direction: 'up' | 'down') {\n const nextIndex = direction === 'up' ? index - 1 : index + 1\n if (nextIndex < 0 || nextIndex >= stages.length) return\n\n const reordered = [...stages]\n const [moved] = reordered.splice(index, 1)\n reordered.splice(nextIndex, 0, moved)\n\n const updated = reordered.map((stage, i) => ({ ...stage, order: i }))\n setStages(updated)\n\n const result = await apiCall('/api/customers/pipeline-stages/reorder', {\n method: 'POST',\n body: JSON.stringify({ stages: updated.map((s) => ({ id: s.id, order: s.order })) }),\n headers: { 'Content-Type': 'application/json' },\n })\n if (!result.ok) {\n flash(t('customers.config.pipelineStages.errorReorder', 'Failed to reorder stages'), 'error')\n if (selectedPipelineId) await loadStages(selectedPipelineId)\n }\n }\n\n const appearanceLabels = React.useMemo<AppearanceSelectorLabels>(() => ({\n colorLabel: t('customers.config.pipelineStages.colorLabel', 'Color'),\n colorHelp: t('customers.config.pipelineStages.colorHelp', 'Pick a highlight color for this entry.'),\n colorClearLabel: t('customers.config.pipelineStages.colorClear', 'Remove color'),\n iconLabel: t('customers.config.pipelineStages.iconLabel', 'Icon'),\n iconPlaceholder: t('customers.config.pipelineStages.iconPlaceholder', 'Type an emoji or pick one of the suggestions.'),\n iconPickerTriggerLabel: t('customers.config.pipelineStages.iconBrowse', 'Browse icons and emojis'),\n iconSearchPlaceholder: t('customers.config.pipelineStages.iconSearchPlaceholder', 'Search icons or emojis\u2026'),\n iconSearchEmptyLabel: t('customers.config.pipelineStages.iconSearchEmpty', 'No icons match your search.'),\n iconSuggestionsLabel: t('customers.config.pipelineStages.iconSuggestions', 'Suggestions'),\n iconClearLabel: t('customers.config.pipelineStages.iconClear', 'Remove icon'),\n previewEmptyLabel: t('customers.config.pipelineStages.previewEmpty', 'None'),\n }), [t])\n\n return (\n <Page>\n <PageBody>\n <div className=\"space-y-6 max-w-2xl\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold\">\n {t('customers.config.pipelineStages.title', 'Pipeline stages')}\n </h2>\n <a\n href=\"/backend/customers/deals/pipeline\"\n className=\"text-sm text-muted-foreground hover:underline\"\n >\n {t('customers.config.pipelineStages.viewBoard', 'View pipeline board')} \u2192\n </a>\n </div>\n\n {loadingPipelines ? (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner size=\"sm\" />\n {t('customers.config.pipelineStages.loadingPipelines', 'Loading pipelines\u2026')}\n </div>\n ) : (\n <>\n <div className=\"flex items-center gap-3\">\n <select\n className=\"flex h-9 w-full max-w-xs rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm focus:outline-none focus:ring-1 focus:ring-ring\"\n value={selectedPipelineId ?? ''}\n onChange={(e) => setSelectedPipelineId(e.target.value || null)}\n >\n {pipelines.length === 0 && (\n <option value=\"\">\n {t('customers.config.pipelineStages.noPipelines', 'No pipelines yet')}\n </option>\n )}\n {pipelines.map((p) => (\n <option key={p.id} value={p.id}>\n {p.name}{p.isDefault ? ` (${t('customers.config.pipelineStages.default', 'default')})` : ''}\n </option>\n ))}\n </select>\n {selectedPipeline && (\n <>\n <Button variant=\"outline\" size=\"sm\" onClick={() => openEditPipeline(selectedPipeline)}>\n {t('customers.config.pipelineStages.editPipeline', 'Edit')}\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"text-destructive\"\n onClick={() => deletePipeline(selectedPipeline)}\n >\n {t('customers.config.pipelineStages.deletePipeline', 'Delete')}\n </Button>\n </>\n )}\n <Button variant=\"outline\" size=\"sm\" onClick={openCreatePipeline}>\n {t('customers.config.pipelineStages.addPipeline', '+ Add pipeline')}\n </Button>\n </div>\n\n {selectedPipelineId && (\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between\">\n <h3 className=\"text-sm font-medium text-muted-foreground\">\n {t('customers.config.pipelineStages.stagesTitle', 'Stages')}\n </h3>\n <Button size=\"sm\" onClick={openCreateStage}>\n {t('customers.config.pipelineStages.addStage', '+ Add stage')}\n </Button>\n </div>\n\n {loadingStages ? (\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Spinner size=\"sm\" />\n {t('customers.config.pipelineStages.loadingStages', 'Loading stages\u2026')}\n </div>\n ) : stages.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('customers.config.pipelineStages.noStages', 'No stages yet. Add your first stage.')}\n </p>\n ) : (\n <div className=\"divide-y rounded-md border\">\n {stages.map((stage, index) => (\n <div key={stage.id} className=\"flex items-center gap-3 px-4 py-3\">\n <div className=\"flex flex-col gap-1\">\n <button\n type=\"button\"\n className=\"text-muted-foreground hover:text-foreground disabled:opacity-30\"\n onClick={() => moveStage(index, 'up')}\n disabled={index === 0}\n aria-label={t('customers.config.pipelineStages.moveUp', 'Move up')}\n >\n \u2191\n </button>\n <button\n type=\"button\"\n className=\"text-muted-foreground hover:text-foreground disabled:opacity-30\"\n onClick={() => moveStage(index, 'down')}\n disabled={index === stages.length - 1}\n aria-label={t('customers.config.pipelineStages.moveDown', 'Move down')}\n >\n \u2193\n </button>\n </div>\n <span className=\"flex-1 text-sm flex items-center gap-2\">\n {stage.color ? renderDictionaryColor(stage.color) : null}\n {stage.icon ? renderDictionaryIcon(stage.icon) : null}\n {stage.label}\n </span>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => openEditStage(stage)}>\n {t('customers.config.pipelineStages.editStage', 'Edit')}\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-destructive\"\n onClick={() => deleteStage(stage)}\n >\n {t('customers.config.pipelineStages.deleteStage', 'Delete')}\n </Button>\n </div>\n ))}\n </div>\n )}\n </div>\n )}\n </>\n )}\n </div>\n\n <Dialog open={pipelineDialog !== null} onOpenChange={(open) => { if (!open) setPipelineDialog(null) }}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {pipelineDialog?.mode === 'create'\n ? t('customers.config.pipelineStages.createPipelineTitle', 'Create pipeline')\n : t('customers.config.pipelineStages.editPipelineTitle', 'Edit pipeline')}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-1\">\n <label className=\"text-sm font-medium\">\n {t('customers.config.pipelineStages.pipelineName', 'Name')}\n </label>\n <Input\n value={pipelineName}\n onChange={(e) => setPipelineName(e.target.value)}\n placeholder={t('customers.config.pipelineStages.pipelineNamePlaceholder', 'e.g. Sales Pipeline')}\n onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); void savePipeline() } }}\n autoFocus\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={pipelineIsDefault}\n onChange={(e) => setPipelineIsDefault(e.target.checked)}\n />\n {t('customers.config.pipelineStages.setAsDefault', 'Set as default pipeline')}\n </label>\n </div>\n <DialogFooter>\n <Button variant=\"outline\" onClick={() => setPipelineDialog(null)} disabled={saving}>\n {t('customers.config.pipelineStages.cancel', 'Cancel')}\n </Button>\n <Button onClick={() => void savePipeline()} disabled={saving || !pipelineName.trim()}>\n {saving ? <Spinner size=\"sm\" /> : t('customers.config.pipelineStages.save', 'Save')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n\n <Dialog open={stageDialog !== null} onOpenChange={(open) => { if (!open) setStageDialog(null) }}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {stageDialog?.mode === 'create'\n ? t('customers.config.pipelineStages.createStageTitle', 'Create stage')\n : t('customers.config.pipelineStages.editStageTitle', 'Edit stage')}\n </DialogTitle>\n </DialogHeader>\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-1\">\n <label className=\"text-sm font-medium\">\n {t('customers.config.pipelineStages.stageName', 'Stage name')}\n </label>\n <Input\n value={stageName}\n onChange={(e) => setStageName(e.target.value)}\n placeholder={t('customers.config.pipelineStages.stageNamePlaceholder', 'e.g. Qualification')}\n onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); void saveStage() } }}\n autoFocus\n />\n </div>\n <AppearanceSelector\n color={stageColor}\n icon={stageIcon}\n onColorChange={setStageColor}\n onIconChange={setStageIcon}\n labels={appearanceLabels}\n iconSuggestions={ICON_SUGGESTIONS}\n />\n </div>\n <DialogFooter>\n <Button variant=\"outline\" onClick={() => setStageDialog(null)} disabled={saving}>\n {t('customers.config.pipelineStages.cancel', 'Cancel')}\n </Button>\n <Button onClick={() => void saveStage()} disabled={saving || !stageName.trim()}>\n {saving ? <Spinner size=\"sm\" /> : t('customers.config.pipelineStages.save', 'Save')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </PageBody>\n </Page>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const metadata = {
|
|
2
|
+
requireAuth: true,
|
|
3
|
+
requireFeatures: ["customers.pipelines.manage"],
|
|
4
|
+
pageTitle: "Pipeline stages",
|
|
5
|
+
pageTitleKey: "customers.config.nav.pipelineStages",
|
|
6
|
+
pageGroup: "Module Configs",
|
|
7
|
+
pageGroupKey: "settings.sections.moduleConfigs",
|
|
8
|
+
pageOrder: 4,
|
|
9
|
+
pageContext: "settings",
|
|
10
|
+
breadcrumb: [
|
|
11
|
+
{ label: "Pipeline stages", labelKey: "customers.config.nav.pipelineStages" }
|
|
12
|
+
]
|
|
13
|
+
};
|
|
14
|
+
export {
|
|
15
|
+
metadata
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=page.meta.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../src/modules/customers/backend/config/customers/pipeline-stages/page.meta.ts"],
|
|
4
|
+
"sourcesContent": ["export const metadata = {\n requireAuth: true,\n requireFeatures: ['customers.pipelines.manage'],\n pageTitle: 'Pipeline stages',\n pageTitleKey: 'customers.config.nav.pipelineStages',\n pageGroup: 'Module Configs',\n pageGroupKey: 'settings.sections.moduleConfigs',\n pageOrder: 4,\n pageContext: 'settings' as const,\n breadcrumb: [\n { label: 'Pipeline stages', labelKey: 'customers.config.nav.pipelineStages' },\n ],\n} as const\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,4BAA4B;AAAA,EAC9C,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,aAAa;AAAA,EACb,YAAY;AAAA,IACV,EAAE,OAAO,mBAAmB,UAAU,sCAAsC;AAAA,EAC9E;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -160,6 +160,8 @@ function DealDetailPage({ params }) {
|
|
|
160
160
|
title: base.title,
|
|
161
161
|
status: base.status ?? void 0,
|
|
162
162
|
pipelineStage: base.pipelineStage ?? void 0,
|
|
163
|
+
pipelineId: base.pipelineId ?? void 0,
|
|
164
|
+
pipelineStageId: base.pipelineStageId ?? void 0,
|
|
163
165
|
valueAmount: typeof base.valueAmount === "number" ? base.valueAmount : void 0,
|
|
164
166
|
valueCurrency: base.valueCurrency ?? void 0,
|
|
165
167
|
probability: typeof base.probability === "number" ? base.probability : void 0,
|
|
@@ -278,7 +280,9 @@ function DealDetailPage({ params }) {
|
|
|
278
280
|
const valueLabel = formatCurrency(data.deal.valueAmount, data.deal.valueCurrency) ?? t("customers.deals.detail.noValue", "Not provided");
|
|
279
281
|
const expectedCloseLabel = formatDate(data.deal.expectedCloseAt, t("customers.deals.detail.noValue", "Not provided"));
|
|
280
282
|
const statusLabel = resolveDictionaryLabel(data.deal.status, statusDictionaryMap) ?? t("customers.deals.detail.noStatus", "No status");
|
|
283
|
+
const statusDictEntry = data.deal.status ? statusDictionaryMap?.[data.deal.status] ?? null : null;
|
|
281
284
|
const pipelineLabel = resolveDictionaryLabel(data.deal.pipelineStage, pipelineDictionaryMap);
|
|
285
|
+
const pipelineDictEntry = data.deal.pipelineStage ? pipelineDictionaryMap?.[data.deal.pipelineStage] ?? null : null;
|
|
282
286
|
const peopleSummaryLabel = data.people.length === 1 ? t("customers.deals.detail.peopleSummaryOne") : t("customers.deals.detail.peopleSummaryMany", void 0, { count: data.people.length });
|
|
283
287
|
const companiesSummaryLabel = data.companies.length === 1 ? t("customers.deals.detail.companiesSummaryOne") : t("customers.deals.detail.companiesSummaryMany", void 0, { count: data.companies.length });
|
|
284
288
|
const viewer = data.viewer ?? null;
|
|
@@ -355,9 +359,21 @@ function DealDetailPage({ params }) {
|
|
|
355
359
|
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase text-muted-foreground", children: t("customers.deals.detail.fields.probability", "Probability") }),
|
|
356
360
|
/* @__PURE__ */ jsx("p", { className: "text-base font-semibold text-foreground", children: probabilityLabel })
|
|
357
361
|
] }),
|
|
362
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
363
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase text-muted-foreground", children: t("customers.deals.detail.fields.status", "Status") }),
|
|
364
|
+
/* @__PURE__ */ jsxs("p", { className: "text-base text-foreground flex items-center gap-2", children: [
|
|
365
|
+
statusDictEntry?.color ? renderDictionaryColor(statusDictEntry.color) : null,
|
|
366
|
+
statusDictEntry?.icon ? renderDictionaryIcon(statusDictEntry.icon) : null,
|
|
367
|
+
statusLabel
|
|
368
|
+
] })
|
|
369
|
+
] }),
|
|
358
370
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
359
371
|
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase text-muted-foreground", children: t("customers.deals.detail.fields.pipeline", "Pipeline stage") }),
|
|
360
|
-
/* @__PURE__ */
|
|
372
|
+
/* @__PURE__ */ jsxs("p", { className: "text-base text-foreground flex items-center gap-2", children: [
|
|
373
|
+
pipelineDictEntry?.color ? renderDictionaryColor(pipelineDictEntry.color) : null,
|
|
374
|
+
pipelineDictEntry?.icon ? renderDictionaryIcon(pipelineDictEntry.icon) : null,
|
|
375
|
+
pipelineLabel ?? t("customers.deals.detail.noValue", "Not provided")
|
|
376
|
+
] })
|
|
361
377
|
] }),
|
|
362
378
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
363
379
|
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase text-muted-foreground", children: t("customers.deals.detail.fields.expectedClose", "Expected close") }),
|
|
@@ -477,6 +493,8 @@ function DealDetailPage({ params }) {
|
|
|
477
493
|
title: data.deal.title ?? "",
|
|
478
494
|
status: data.deal.status ?? "",
|
|
479
495
|
pipelineStage: data.deal.pipelineStage ?? "",
|
|
496
|
+
pipelineId: data.deal.pipelineId ?? "",
|
|
497
|
+
pipelineStageId: data.deal.pipelineStageId ?? "",
|
|
480
498
|
valueAmount: data.deal.valueAmount ? Number(data.deal.valueAmount) : null,
|
|
481
499
|
valueCurrency: data.deal.valueCurrency ?? void 0,
|
|
482
500
|
probability: data.deal.probability ?? null,
|