@open-mercato/core 0.4.5-develop-0f0e676c72 → 0.4.5-develop-e694581d9f
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/translations/api/[entityType]/[entityId]/route.js +46 -44
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
- package/dist/modules/translations/api/context.js +10 -1
- package/dist/modules/translations/api/context.js.map +2 -2
- package/dist/modules/translations/commands/index.js +2 -0
- package/dist/modules/translations/commands/index.js.map +7 -0
- package/dist/modules/translations/commands/translations.js +160 -0
- package/dist/modules/translations/commands/translations.js.map +7 -0
- package/dist/modules/translations/index.js +1 -0
- package/dist/modules/translations/index.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/translations/api/[entityType]/[entityId]/route.ts +65 -60
- package/src/modules/translations/api/context.ts +12 -0
- package/src/modules/translations/commands/index.ts +1 -0
- package/src/modules/translations/commands/translations.ts +253 -0
- package/src/modules/translations/index.ts +1 -0
- 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,
|