@streamoid/catalogix-chat 0.1.0 → 0.2.0
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/index.d.ts +84 -14
- package/dist/index.js +3586 -27
- package/package.json +19 -2
package/dist/index.js
CHANGED
|
@@ -1,40 +1,3594 @@
|
|
|
1
1
|
// src/CatalogixChat.tsx
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
import { useEffect as useEffect7 } from "react";
|
|
3
|
+
|
|
4
|
+
// src/api.ts
|
|
5
|
+
var _config = {
|
|
6
|
+
catalogixBaseUrl: "",
|
|
7
|
+
keplerProxyPrefix: "/kepler-api"
|
|
8
|
+
};
|
|
9
|
+
function configureApi(config) {
|
|
10
|
+
_config = { ..._config, ...config };
|
|
11
|
+
}
|
|
12
|
+
function getApiConfig() {
|
|
13
|
+
return _config;
|
|
14
|
+
}
|
|
15
|
+
async function fetchJson(url, options) {
|
|
16
|
+
const response = await fetch(url, {
|
|
17
|
+
credentials: "include",
|
|
18
|
+
...options,
|
|
19
|
+
headers: {
|
|
20
|
+
Accept: "application/json",
|
|
21
|
+
...options?.headers || {}
|
|
13
22
|
}
|
|
14
|
-
}
|
|
15
|
-
|
|
23
|
+
});
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
throw new Error(`API call failed: ${response.status} ${response.statusText}`);
|
|
26
|
+
}
|
|
27
|
+
return response.json();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/SelectProducts/index.tsx
|
|
31
|
+
import { useCallback as useCallback4, useEffect as useEffect3, useMemo as useMemo3, useRef as useRef4, useState as useState5 } from "react";
|
|
32
|
+
import { CheckCircle, Loader2 as Loader23, Search as Search2 } from "lucide-react";
|
|
33
|
+
|
|
34
|
+
// src/ui/button.tsx
|
|
35
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
36
|
+
import { cva } from "class-variance-authority";
|
|
37
|
+
|
|
38
|
+
// src/ui/utils.ts
|
|
39
|
+
import { clsx } from "clsx";
|
|
40
|
+
import { twMerge } from "tailwind-merge";
|
|
41
|
+
function cn(...inputs) {
|
|
42
|
+
return twMerge(clsx(inputs));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/ui/button.tsx
|
|
46
|
+
import { jsx } from "react/jsx-runtime";
|
|
47
|
+
var buttonVariants = cva(
|
|
48
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
49
|
+
{
|
|
50
|
+
variants: {
|
|
51
|
+
variant: {
|
|
52
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
53
|
+
destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
54
|
+
outline: "border bg-background text-foreground hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
|
55
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
56
|
+
ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
57
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
58
|
+
},
|
|
59
|
+
size: {
|
|
60
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
61
|
+
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
62
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
63
|
+
icon: "size-9 rounded-md"
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
defaultVariants: {
|
|
67
|
+
variant: "default",
|
|
68
|
+
size: "default"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
function Button({
|
|
73
|
+
className,
|
|
74
|
+
variant,
|
|
75
|
+
size,
|
|
76
|
+
asChild = false,
|
|
77
|
+
...props
|
|
78
|
+
}) {
|
|
79
|
+
const Comp = asChild ? Slot : "button";
|
|
80
|
+
return /* @__PURE__ */ jsx(
|
|
81
|
+
Comp,
|
|
82
|
+
{
|
|
83
|
+
"data-slot": "button",
|
|
84
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
85
|
+
...props
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/ui/input.tsx
|
|
91
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
92
|
+
function Input({ className, type, ...props }) {
|
|
93
|
+
return /* @__PURE__ */ jsx2(
|
|
94
|
+
"input",
|
|
95
|
+
{
|
|
96
|
+
type,
|
|
97
|
+
"data-slot": "input",
|
|
98
|
+
className: cn(
|
|
99
|
+
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border px-3 py-1 text-base bg-input-background transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
100
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
101
|
+
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
102
|
+
className
|
|
103
|
+
),
|
|
104
|
+
...props
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/ui/card.tsx
|
|
110
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
111
|
+
function Card({ className, ...props }) {
|
|
112
|
+
return /* @__PURE__ */ jsx3(
|
|
16
113
|
"div",
|
|
17
114
|
{
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
115
|
+
"data-slot": "card",
|
|
116
|
+
className: cn(
|
|
117
|
+
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border",
|
|
118
|
+
className
|
|
119
|
+
),
|
|
120
|
+
...props
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/SelectProducts/ProductTable.tsx
|
|
126
|
+
import { useCallback as useCallback3, useEffect as useEffect2, useMemo, useRef as useRef3, useState as useState3 } from "react";
|
|
127
|
+
import {
|
|
128
|
+
flexRender,
|
|
129
|
+
getCoreRowModel,
|
|
130
|
+
useReactTable
|
|
131
|
+
} from "@tanstack/react-table";
|
|
132
|
+
import { ChevronLeft, ChevronRight, Package, Layers } from "lucide-react";
|
|
133
|
+
|
|
134
|
+
// src/ui/table.tsx
|
|
135
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
136
|
+
function Table({ className, ...props }) {
|
|
137
|
+
return /* @__PURE__ */ jsx4(
|
|
138
|
+
"div",
|
|
139
|
+
{
|
|
140
|
+
"data-slot": "table-container",
|
|
141
|
+
className: "relative w-full overflow-x-auto",
|
|
142
|
+
children: /* @__PURE__ */ jsx4(
|
|
143
|
+
"table",
|
|
144
|
+
{
|
|
145
|
+
"data-slot": "table",
|
|
146
|
+
className: cn("w-full caption-bottom text-sm", className),
|
|
147
|
+
...props
|
|
148
|
+
}
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
function TableHeader({ className, ...props }) {
|
|
154
|
+
return /* @__PURE__ */ jsx4(
|
|
155
|
+
"thead",
|
|
156
|
+
{
|
|
157
|
+
"data-slot": "table-header",
|
|
158
|
+
className: cn("[&_tr]:border-b", className),
|
|
159
|
+
...props
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
function TableBody({ className, ...props }) {
|
|
164
|
+
return /* @__PURE__ */ jsx4(
|
|
165
|
+
"tbody",
|
|
166
|
+
{
|
|
167
|
+
"data-slot": "table-body",
|
|
168
|
+
className: cn("[&_tr:last-child]:border-0", className),
|
|
169
|
+
...props
|
|
170
|
+
}
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
function TableRow({ className, ...props }) {
|
|
174
|
+
return /* @__PURE__ */ jsx4(
|
|
175
|
+
"tr",
|
|
176
|
+
{
|
|
177
|
+
"data-slot": "table-row",
|
|
178
|
+
className: cn(
|
|
179
|
+
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
|
|
180
|
+
className
|
|
181
|
+
),
|
|
182
|
+
...props
|
|
183
|
+
}
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
function TableHead({ className, ...props }) {
|
|
187
|
+
return /* @__PURE__ */ jsx4(
|
|
188
|
+
"th",
|
|
189
|
+
{
|
|
190
|
+
"data-slot": "table-head",
|
|
191
|
+
className: cn(
|
|
192
|
+
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
193
|
+
className
|
|
194
|
+
),
|
|
195
|
+
...props
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
function TableCell({ className, ...props }) {
|
|
200
|
+
return /* @__PURE__ */ jsx4(
|
|
201
|
+
"td",
|
|
202
|
+
{
|
|
203
|
+
"data-slot": "table-cell",
|
|
204
|
+
className: cn(
|
|
205
|
+
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
|
206
|
+
className
|
|
207
|
+
),
|
|
208
|
+
...props
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/ui/checkbox.tsx
|
|
214
|
+
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
|
215
|
+
import { CheckIcon } from "lucide-react";
|
|
216
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
217
|
+
function Checkbox({
|
|
218
|
+
className,
|
|
219
|
+
...props
|
|
220
|
+
}) {
|
|
221
|
+
return /* @__PURE__ */ jsx5(
|
|
222
|
+
CheckboxPrimitive.Root,
|
|
223
|
+
{
|
|
224
|
+
"data-slot": "checkbox",
|
|
225
|
+
className: cn(
|
|
226
|
+
"peer border border-muted-foreground/40 bg-input-background dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
|
227
|
+
className
|
|
228
|
+
),
|
|
229
|
+
...props,
|
|
230
|
+
children: /* @__PURE__ */ jsx5(
|
|
231
|
+
CheckboxPrimitive.Indicator,
|
|
232
|
+
{
|
|
233
|
+
"data-slot": "checkbox-indicator",
|
|
234
|
+
className: "flex items-center justify-center text-current transition-none",
|
|
235
|
+
children: /* @__PURE__ */ jsx5(CheckIcon, { className: "size-3.5" })
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// src/ui/badge.tsx
|
|
243
|
+
import { Slot as Slot2 } from "@radix-ui/react-slot";
|
|
244
|
+
import { cva as cva2 } from "class-variance-authority";
|
|
245
|
+
import { jsx as jsx6 } from "react/jsx-runtime";
|
|
246
|
+
var badgeVariants = cva2(
|
|
247
|
+
"inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
|
|
248
|
+
{
|
|
249
|
+
variants: {
|
|
250
|
+
variant: {
|
|
251
|
+
default: "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
|
252
|
+
secondary: "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
|
253
|
+
destructive: "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
254
|
+
outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground"
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
defaultVariants: {
|
|
258
|
+
variant: "default"
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
function Badge({
|
|
263
|
+
className,
|
|
264
|
+
variant,
|
|
265
|
+
asChild = false,
|
|
266
|
+
...props
|
|
267
|
+
}) {
|
|
268
|
+
const Comp = asChild ? Slot2 : "span";
|
|
269
|
+
return /* @__PURE__ */ jsx6(
|
|
270
|
+
Comp,
|
|
271
|
+
{
|
|
272
|
+
"data-slot": "badge",
|
|
273
|
+
className: cn(badgeVariants({ variant }), className),
|
|
274
|
+
...props
|
|
275
|
+
}
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/ui/skeleton.tsx
|
|
280
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
281
|
+
function Skeleton({ className, ...props }) {
|
|
282
|
+
return /* @__PURE__ */ jsx7(
|
|
283
|
+
"div",
|
|
284
|
+
{
|
|
285
|
+
"data-slot": "skeleton",
|
|
286
|
+
className: cn("bg-accent animate-pulse rounded-md", className),
|
|
287
|
+
...props
|
|
288
|
+
}
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/SelectProducts/ImageCell.tsx
|
|
293
|
+
import { useCallback, useRef, useState } from "react";
|
|
294
|
+
import { createPortal } from "react-dom";
|
|
295
|
+
|
|
296
|
+
// src/ui/dialog.tsx
|
|
297
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
298
|
+
import { XIcon } from "lucide-react";
|
|
299
|
+
import { jsx as jsx8, jsxs } from "react/jsx-runtime";
|
|
300
|
+
function Dialog({
|
|
301
|
+
...props
|
|
302
|
+
}) {
|
|
303
|
+
return /* @__PURE__ */ jsx8(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
|
|
304
|
+
}
|
|
305
|
+
function DialogPortal({
|
|
306
|
+
...props
|
|
307
|
+
}) {
|
|
308
|
+
return /* @__PURE__ */ jsx8(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
|
|
309
|
+
}
|
|
310
|
+
function DialogOverlay({
|
|
311
|
+
className,
|
|
312
|
+
...props
|
|
313
|
+
}) {
|
|
314
|
+
return /* @__PURE__ */ jsx8(
|
|
315
|
+
DialogPrimitive.Overlay,
|
|
316
|
+
{
|
|
317
|
+
"data-slot": "dialog-overlay",
|
|
318
|
+
className: cn(
|
|
319
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/70",
|
|
320
|
+
className
|
|
321
|
+
),
|
|
322
|
+
...props
|
|
323
|
+
}
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
function DialogContent({
|
|
327
|
+
className,
|
|
328
|
+
children,
|
|
329
|
+
...props
|
|
330
|
+
}) {
|
|
331
|
+
return /* @__PURE__ */ jsxs(DialogPortal, { "data-slot": "dialog-portal", children: [
|
|
332
|
+
/* @__PURE__ */ jsx8(DialogOverlay, {}),
|
|
333
|
+
/* @__PURE__ */ jsxs(
|
|
334
|
+
DialogPrimitive.Content,
|
|
335
|
+
{
|
|
336
|
+
"data-slot": "dialog-content",
|
|
337
|
+
className: cn(
|
|
338
|
+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
|
|
339
|
+
className
|
|
340
|
+
),
|
|
341
|
+
...props,
|
|
342
|
+
children: [
|
|
343
|
+
children,
|
|
344
|
+
/* @__PURE__ */ jsxs(DialogPrimitive.Close, { className: "ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", children: [
|
|
345
|
+
/* @__PURE__ */ jsx8(XIcon, {}),
|
|
346
|
+
/* @__PURE__ */ jsx8("span", { className: "sr-only", children: "Close" })
|
|
347
|
+
] })
|
|
348
|
+
]
|
|
349
|
+
}
|
|
350
|
+
)
|
|
351
|
+
] });
|
|
352
|
+
}
|
|
353
|
+
function DialogHeader({ className, ...props }) {
|
|
354
|
+
return /* @__PURE__ */ jsx8(
|
|
355
|
+
"div",
|
|
356
|
+
{
|
|
357
|
+
"data-slot": "dialog-header",
|
|
358
|
+
className: cn("flex flex-col gap-2 text-center sm:text-left", className),
|
|
359
|
+
...props
|
|
360
|
+
}
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
function DialogTitle({
|
|
364
|
+
className,
|
|
365
|
+
...props
|
|
366
|
+
}) {
|
|
367
|
+
return /* @__PURE__ */ jsx8(
|
|
368
|
+
DialogPrimitive.Title,
|
|
369
|
+
{
|
|
370
|
+
"data-slot": "dialog-title",
|
|
371
|
+
className: cn("text-lg leading-none font-semibold", className),
|
|
372
|
+
...props
|
|
373
|
+
}
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
function DialogDescription({
|
|
377
|
+
className,
|
|
378
|
+
...props
|
|
379
|
+
}) {
|
|
380
|
+
return /* @__PURE__ */ jsx8(
|
|
381
|
+
DialogPrimitive.Description,
|
|
382
|
+
{
|
|
383
|
+
"data-slot": "dialog-description",
|
|
384
|
+
className: cn("text-muted-foreground text-sm", className),
|
|
385
|
+
...props
|
|
386
|
+
}
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/SelectProducts/ImageCell.tsx
|
|
391
|
+
import { Fragment, jsx as jsx9, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
392
|
+
function thumbnailUrl(src) {
|
|
393
|
+
if (!src) return "";
|
|
394
|
+
const cleaned = src.replace("dl=0", "raw=1").replace("dl=1", "raw=1");
|
|
395
|
+
return cleaned.includes("?") ? `${cleaned}&format=webp&w=120` : `${cleaned}?format=webp&w=120`;
|
|
396
|
+
}
|
|
397
|
+
function fullSizeUrl(src) {
|
|
398
|
+
if (!src) return "";
|
|
399
|
+
const cleaned = src.replace("dl=0", "raw=1").replace("dl=1", "raw=1");
|
|
400
|
+
return cleaned.includes("?") ? `${cleaned}&format=webp&w=600` : `${cleaned}?format=webp&w=600`;
|
|
401
|
+
}
|
|
402
|
+
function ImageCell({ src }) {
|
|
403
|
+
const [hovered, setHovered] = useState(false);
|
|
404
|
+
const [dialogOpen, setDialogOpen] = useState(false);
|
|
405
|
+
const cellRef = useRef(null);
|
|
406
|
+
const [zoomPos, setZoomPos] = useState({ top: 0, left: 0 });
|
|
407
|
+
const handleMouseEnter = useCallback(() => {
|
|
408
|
+
if (!cellRef.current || !src) return;
|
|
409
|
+
const rect = cellRef.current.getBoundingClientRect();
|
|
410
|
+
setZoomPos({
|
|
411
|
+
top: rect.top,
|
|
412
|
+
left: rect.right + 8
|
|
413
|
+
});
|
|
414
|
+
setHovered(true);
|
|
415
|
+
}, [src]);
|
|
416
|
+
const handleMouseLeave = useCallback(() => {
|
|
417
|
+
setHovered(false);
|
|
418
|
+
}, []);
|
|
419
|
+
if (!src) {
|
|
420
|
+
return /* @__PURE__ */ jsx9("div", { className: "flex h-[60px] w-[50px] items-center justify-center rounded bg-muted text-xs text-muted-foreground", children: "No image" });
|
|
421
|
+
}
|
|
422
|
+
return /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
423
|
+
/* @__PURE__ */ jsx9(
|
|
424
|
+
"div",
|
|
425
|
+
{
|
|
426
|
+
ref: cellRef,
|
|
427
|
+
className: "relative cursor-pointer",
|
|
428
|
+
onMouseEnter: handleMouseEnter,
|
|
429
|
+
onMouseLeave: handleMouseLeave,
|
|
430
|
+
onClick: () => setDialogOpen(true),
|
|
431
|
+
children: /* @__PURE__ */ jsx9(
|
|
432
|
+
"img",
|
|
433
|
+
{
|
|
434
|
+
src: thumbnailUrl(src),
|
|
435
|
+
alt: "",
|
|
436
|
+
className: "h-[60px] w-[50px] rounded-md object-cover transition-shadow hover:shadow-md",
|
|
437
|
+
loading: "lazy"
|
|
438
|
+
}
|
|
439
|
+
)
|
|
440
|
+
}
|
|
441
|
+
),
|
|
442
|
+
hovered && createPortal(
|
|
443
|
+
/* @__PURE__ */ jsx9(
|
|
444
|
+
"div",
|
|
445
|
+
{
|
|
446
|
+
className: "pointer-events-none fixed z-[9999] rounded-lg border bg-background shadow-xl",
|
|
447
|
+
style: {
|
|
448
|
+
top: zoomPos.top,
|
|
449
|
+
left: zoomPos.left,
|
|
450
|
+
maxWidth: 400
|
|
451
|
+
},
|
|
452
|
+
children: /* @__PURE__ */ jsx9(
|
|
453
|
+
"img",
|
|
454
|
+
{
|
|
455
|
+
src: fullSizeUrl(src),
|
|
456
|
+
alt: "",
|
|
457
|
+
className: "max-h-[400px] max-w-[400px] rounded-lg object-contain"
|
|
458
|
+
}
|
|
459
|
+
)
|
|
460
|
+
}
|
|
461
|
+
),
|
|
462
|
+
document.body
|
|
463
|
+
),
|
|
464
|
+
/* @__PURE__ */ jsx9(Dialog, { open: dialogOpen, onOpenChange: setDialogOpen, children: /* @__PURE__ */ jsxs2(DialogContent, { className: "max-w-2xl", children: [
|
|
465
|
+
/* @__PURE__ */ jsx9(DialogHeader, { children: /* @__PURE__ */ jsx9(DialogTitle, { children: "Image Preview" }) }),
|
|
466
|
+
/* @__PURE__ */ jsx9("div", { className: "flex items-center justify-center p-2", children: /* @__PURE__ */ jsx9(
|
|
467
|
+
"img",
|
|
468
|
+
{
|
|
469
|
+
src: fullSizeUrl(src),
|
|
470
|
+
alt: "",
|
|
471
|
+
className: "max-h-[70vh] max-w-full rounded-md object-contain"
|
|
472
|
+
}
|
|
473
|
+
) })
|
|
474
|
+
] }) })
|
|
475
|
+
] });
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/SelectProducts/EditableCell.tsx
|
|
479
|
+
import { useCallback as useCallback2, useEffect, useRef as useRef2, useState as useState2 } from "react";
|
|
480
|
+
import { Check, ChevronsUpDown, Pencil, Loader2 } from "lucide-react";
|
|
481
|
+
|
|
482
|
+
// src/ui/popover.tsx
|
|
483
|
+
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
484
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
485
|
+
function Popover({
|
|
486
|
+
...props
|
|
487
|
+
}) {
|
|
488
|
+
return /* @__PURE__ */ jsx10(PopoverPrimitive.Root, { "data-slot": "popover", ...props });
|
|
489
|
+
}
|
|
490
|
+
function PopoverTrigger({
|
|
491
|
+
...props
|
|
492
|
+
}) {
|
|
493
|
+
return /* @__PURE__ */ jsx10(PopoverPrimitive.Trigger, { "data-slot": "popover-trigger", ...props });
|
|
494
|
+
}
|
|
495
|
+
function PopoverContent({
|
|
496
|
+
className,
|
|
497
|
+
align = "center",
|
|
498
|
+
sideOffset = 4,
|
|
499
|
+
...props
|
|
500
|
+
}) {
|
|
501
|
+
return /* @__PURE__ */ jsx10(PopoverPrimitive.Portal, { children: /* @__PURE__ */ jsx10(
|
|
502
|
+
PopoverPrimitive.Content,
|
|
503
|
+
{
|
|
504
|
+
"data-slot": "popover-content",
|
|
505
|
+
align,
|
|
506
|
+
sideOffset,
|
|
507
|
+
className: cn(
|
|
508
|
+
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
|
|
509
|
+
className
|
|
510
|
+
),
|
|
511
|
+
...props
|
|
512
|
+
}
|
|
513
|
+
) });
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// src/ui/command.tsx
|
|
517
|
+
import { Command as CommandPrimitive } from "cmdk";
|
|
518
|
+
import { SearchIcon } from "lucide-react";
|
|
519
|
+
import { jsx as jsx11, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
520
|
+
function Command({
|
|
521
|
+
className,
|
|
522
|
+
...props
|
|
523
|
+
}) {
|
|
524
|
+
return /* @__PURE__ */ jsx11(
|
|
525
|
+
CommandPrimitive,
|
|
526
|
+
{
|
|
527
|
+
"data-slot": "command",
|
|
528
|
+
className: cn(
|
|
529
|
+
"bg-popover text-popover-foreground flex h-full w-full flex-col overflow-hidden rounded-md",
|
|
530
|
+
className
|
|
531
|
+
),
|
|
532
|
+
...props
|
|
533
|
+
}
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
function CommandInput({
|
|
537
|
+
className,
|
|
538
|
+
...props
|
|
539
|
+
}) {
|
|
540
|
+
return /* @__PURE__ */ jsxs3(
|
|
541
|
+
"div",
|
|
542
|
+
{
|
|
543
|
+
"data-slot": "command-input-wrapper",
|
|
544
|
+
className: "flex h-9 items-center gap-2 border-b px-3",
|
|
545
|
+
children: [
|
|
546
|
+
/* @__PURE__ */ jsx11(SearchIcon, { className: "size-4 shrink-0 opacity-50" }),
|
|
547
|
+
/* @__PURE__ */ jsx11(
|
|
548
|
+
CommandPrimitive.Input,
|
|
549
|
+
{
|
|
550
|
+
"data-slot": "command-input",
|
|
551
|
+
className: cn(
|
|
552
|
+
"placeholder:text-muted-foreground flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden disabled:cursor-not-allowed disabled:opacity-50",
|
|
553
|
+
className
|
|
554
|
+
),
|
|
555
|
+
...props
|
|
556
|
+
}
|
|
557
|
+
)
|
|
558
|
+
]
|
|
559
|
+
}
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
function CommandList({
|
|
563
|
+
className,
|
|
564
|
+
...props
|
|
565
|
+
}) {
|
|
566
|
+
return /* @__PURE__ */ jsx11(
|
|
567
|
+
CommandPrimitive.List,
|
|
568
|
+
{
|
|
569
|
+
"data-slot": "command-list",
|
|
570
|
+
className: cn(
|
|
571
|
+
"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto",
|
|
572
|
+
className
|
|
573
|
+
),
|
|
574
|
+
...props
|
|
575
|
+
}
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
function CommandEmpty({
|
|
579
|
+
...props
|
|
580
|
+
}) {
|
|
581
|
+
return /* @__PURE__ */ jsx11(
|
|
582
|
+
CommandPrimitive.Empty,
|
|
583
|
+
{
|
|
584
|
+
"data-slot": "command-empty",
|
|
585
|
+
className: "py-6 text-center text-sm",
|
|
586
|
+
...props
|
|
587
|
+
}
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
function CommandGroup({
|
|
591
|
+
className,
|
|
592
|
+
...props
|
|
593
|
+
}) {
|
|
594
|
+
return /* @__PURE__ */ jsx11(
|
|
595
|
+
CommandPrimitive.Group,
|
|
596
|
+
{
|
|
597
|
+
"data-slot": "command-group",
|
|
598
|
+
className: cn(
|
|
599
|
+
"text-foreground [&_[cmdk-group-heading]]:text-muted-foreground overflow-hidden p-1 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium",
|
|
600
|
+
className
|
|
601
|
+
),
|
|
602
|
+
...props
|
|
603
|
+
}
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
function CommandItem({
|
|
607
|
+
className,
|
|
608
|
+
...props
|
|
609
|
+
}) {
|
|
610
|
+
return /* @__PURE__ */ jsx11(
|
|
611
|
+
CommandPrimitive.Item,
|
|
612
|
+
{
|
|
613
|
+
"data-slot": "command-item",
|
|
614
|
+
className: cn(
|
|
615
|
+
"data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
616
|
+
className
|
|
617
|
+
),
|
|
618
|
+
...props
|
|
619
|
+
}
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// src/SelectProducts/utils.ts
|
|
624
|
+
var PRODUCTS_PER_PAGE = 50;
|
|
625
|
+
var DEFAULT_SOURCE_MP = "SMP";
|
|
626
|
+
var DEFAULT_COLUMNS = [
|
|
627
|
+
{ name: "Image URL 1", value: "Image URL 1", channel: "SMP" },
|
|
628
|
+
{ name: "Image URL 2", value: "Image URL 2", channel: "SMP" },
|
|
629
|
+
{ name: "SKU Code", value: "SKU Code", channel: "SMP" },
|
|
630
|
+
{ name: "Style Code", value: "Style Code", channel: "SMP" },
|
|
631
|
+
{ name: "Category", value: "Category", channel: "SMP" },
|
|
632
|
+
{ name: "Department", value: "Department", channel: "SMP" },
|
|
633
|
+
{ name: "Status", value: "status", channel: "required" },
|
|
634
|
+
{ name: "updated on", value: "modified_ts", channel: "SMP" }
|
|
635
|
+
];
|
|
636
|
+
var CORE_COLUMN_VALUES = /* @__PURE__ */ new Set([
|
|
637
|
+
"Image URL 1",
|
|
638
|
+
"Image URL 2",
|
|
639
|
+
"SKU Code",
|
|
640
|
+
"Style Code",
|
|
641
|
+
"Category",
|
|
642
|
+
"Department",
|
|
643
|
+
"status",
|
|
644
|
+
"modified_ts"
|
|
645
|
+
]);
|
|
646
|
+
var NON_IMAGE_KEYS = /* @__PURE__ */ new Set(["image_count"]);
|
|
647
|
+
function buildFromRequiredFields(data) {
|
|
648
|
+
const columns = [];
|
|
649
|
+
const allowedValues = {};
|
|
650
|
+
const smpFields = data?.SMP;
|
|
651
|
+
if (smpFields && typeof smpFields === "object") {
|
|
652
|
+
const templateMap = {};
|
|
653
|
+
for (const [fieldName, fieldDef] of Object.entries(smpFields)) {
|
|
654
|
+
if (fieldName === "product_code" || fieldName === "store_uuid") continue;
|
|
655
|
+
columns.push({
|
|
656
|
+
name: fieldName,
|
|
657
|
+
value: fieldName,
|
|
658
|
+
channel: "SMP"
|
|
659
|
+
});
|
|
660
|
+
templateMap[fieldName] = fieldDef;
|
|
661
|
+
}
|
|
662
|
+
allowedValues.SMP = { _all: templateMap };
|
|
663
|
+
}
|
|
664
|
+
const productFields = data?.required;
|
|
665
|
+
if (productFields && typeof productFields === "object") {
|
|
666
|
+
const productMap = {};
|
|
667
|
+
for (const [fieldName, fieldDef] of Object.entries(productFields)) {
|
|
668
|
+
if (fieldName === "product_code" || fieldName === "store_uuid") continue;
|
|
669
|
+
if ("values" in fieldDef && fieldDef.values) {
|
|
670
|
+
productMap[fieldName] = { values: fieldDef.values };
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (Object.keys(productMap).length > 0) {
|
|
674
|
+
allowedValues.product = productMap;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return { columns, allowedValues };
|
|
678
|
+
}
|
|
679
|
+
function buildFieldsFromColumns(columns, sourceMarketplace = DEFAULT_SOURCE_MP) {
|
|
680
|
+
const fields = {};
|
|
681
|
+
for (const col of columns) {
|
|
682
|
+
const channel = col.channel || sourceMarketplace;
|
|
683
|
+
if (!fields[channel]) fields[channel] = [];
|
|
684
|
+
fields[channel].push(col.value, `${col.value}_automated`);
|
|
685
|
+
}
|
|
686
|
+
if (!fields.required) fields.required = [];
|
|
687
|
+
const requiredDefaults = [
|
|
688
|
+
"status",
|
|
689
|
+
"status_automated",
|
|
690
|
+
"assignee_user_id",
|
|
691
|
+
"assignee_user_id_automated",
|
|
692
|
+
"product_uuid",
|
|
693
|
+
"store_uuid"
|
|
694
|
+
];
|
|
695
|
+
fields.required = Array.from(/* @__PURE__ */ new Set([...fields.required, ...requiredDefaults]));
|
|
696
|
+
return fields;
|
|
697
|
+
}
|
|
698
|
+
function buildFiltersFromFields(fields) {
|
|
699
|
+
const filters = {};
|
|
700
|
+
for (const channel of Object.keys(fields)) {
|
|
701
|
+
filters[channel] = {};
|
|
702
|
+
}
|
|
703
|
+
return filters;
|
|
704
|
+
}
|
|
705
|
+
function formatProducts(groups) {
|
|
706
|
+
const rows = [];
|
|
707
|
+
for (const group of groups) {
|
|
708
|
+
const channels = Object.keys(group.data);
|
|
709
|
+
const variantsByIndex = [];
|
|
710
|
+
for (const channel of channels) {
|
|
711
|
+
const items = group.data[channel];
|
|
712
|
+
if (!items?.length) continue;
|
|
713
|
+
items.forEach((item, idx) => {
|
|
714
|
+
const flat = {};
|
|
715
|
+
for (const [key, val] of Object.entries(item)) {
|
|
716
|
+
if (key.endsWith("_details") || key === "_id" || key === "ov") continue;
|
|
717
|
+
flat[`${key}::${channel}`] = val;
|
|
718
|
+
}
|
|
719
|
+
if (variantsByIndex[idx]) {
|
|
720
|
+
Object.assign(variantsByIndex[idx], flat);
|
|
721
|
+
} else {
|
|
722
|
+
variantsByIndex[idx] = flat;
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
for (const variant of variantsByIndex) {
|
|
727
|
+
if (!variant) continue;
|
|
728
|
+
const productCode = variant["product_code::product"] || variant["product_code::SMP"] || "";
|
|
729
|
+
const productUuid = variant["product_uuid::product"] || variant["product_uuid::SMP"] || "";
|
|
730
|
+
const storeUuid = variant["store_uuid::product"] || variant["store_uuid::SMP"] || "";
|
|
731
|
+
const parentCode = variant["parent_code::product"] || group.group_id || "";
|
|
732
|
+
const status = variant["status::product"] || "";
|
|
733
|
+
const template = variant["template::SMP"] || "";
|
|
734
|
+
rows.push({
|
|
735
|
+
...variant,
|
|
736
|
+
id: `${productCode}==${storeUuid}`,
|
|
737
|
+
product_code: productCode,
|
|
738
|
+
product_uuid: productUuid,
|
|
739
|
+
store_uuid: storeUuid,
|
|
740
|
+
parent_code: parentCode,
|
|
741
|
+
status,
|
|
742
|
+
template
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
return rows;
|
|
747
|
+
}
|
|
748
|
+
function formatProductsByStyle(rows) {
|
|
749
|
+
const seen = /* @__PURE__ */ new Map();
|
|
750
|
+
for (const row of rows) {
|
|
751
|
+
const key = row.parent_code || row.product_code;
|
|
752
|
+
if (!seen.has(key)) {
|
|
753
|
+
seen.set(key, { ...row, _variantCount: 1 });
|
|
754
|
+
} else {
|
|
755
|
+
seen.get(key)._variantCount = (seen.get(key)._variantCount || 1) + 1;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return Array.from(seen.values());
|
|
759
|
+
}
|
|
760
|
+
function getCellType(columnValue, channel, allowedValues, template) {
|
|
761
|
+
const lower = columnValue.toLowerCase();
|
|
762
|
+
if ((lower.includes("image") || lower.includes("url")) && !NON_IMAGE_KEYS.has(lower)) {
|
|
763
|
+
return "image";
|
|
764
|
+
}
|
|
765
|
+
if (lower === "modified_ts" || lower.includes("_ts")) {
|
|
766
|
+
return "timestamp";
|
|
767
|
+
}
|
|
768
|
+
const values = getDropdownValues(allowedValues, template, columnValue, channel);
|
|
769
|
+
if (values && values.length > 0) {
|
|
770
|
+
return "select";
|
|
771
|
+
}
|
|
772
|
+
return "text";
|
|
773
|
+
}
|
|
774
|
+
function getDropdownValues(allowedValues, template, fieldName, channel) {
|
|
775
|
+
if (!allowedValues) return null;
|
|
776
|
+
const apiChannel = channel === "required" ? "product" : channel;
|
|
777
|
+
const channelValues = allowedValues[apiChannel];
|
|
778
|
+
if (!channelValues) return null;
|
|
779
|
+
if (apiChannel === "product") {
|
|
780
|
+
const fieldDef = channelValues[fieldName];
|
|
781
|
+
return fieldDef?.values ?? null;
|
|
782
|
+
}
|
|
783
|
+
if (template && channelValues[template]) {
|
|
784
|
+
const templateValues = channelValues[template];
|
|
785
|
+
const fieldDef = templateValues[fieldName];
|
|
786
|
+
if (fieldDef?.values) return fieldDef.values;
|
|
787
|
+
}
|
|
788
|
+
if (channelValues._all) {
|
|
789
|
+
const allValues = channelValues._all;
|
|
790
|
+
const fieldDef = allValues[fieldName];
|
|
791
|
+
if (fieldDef?.values) return fieldDef.values;
|
|
792
|
+
}
|
|
793
|
+
return null;
|
|
794
|
+
}
|
|
795
|
+
function formatTimestamp(value) {
|
|
796
|
+
if (!value) return "";
|
|
797
|
+
const num = typeof value === "number" ? value : Number(value);
|
|
798
|
+
if (Number.isNaN(num)) return String(value);
|
|
799
|
+
const date = new Date(num * 1e3);
|
|
800
|
+
const timeStr = date.toLocaleTimeString("en-US", {
|
|
801
|
+
hour: "numeric",
|
|
802
|
+
minute: "2-digit",
|
|
803
|
+
hour12: true
|
|
804
|
+
});
|
|
805
|
+
const dateStr = date.toLocaleDateString("en-US", {
|
|
806
|
+
month: "2-digit",
|
|
807
|
+
day: "2-digit"
|
|
808
|
+
});
|
|
809
|
+
return `${timeStr} ${dateStr}`;
|
|
810
|
+
}
|
|
811
|
+
function getCellValue(row, columnValue, channel) {
|
|
812
|
+
const apiChannel = channel === "required" ? "product" : channel;
|
|
813
|
+
const namespacedKey = `${columnValue}::${apiChannel}`;
|
|
814
|
+
if (row[namespacedKey] !== void 0) return row[namespacedKey];
|
|
815
|
+
if (row[columnValue] !== void 0) return row[columnValue];
|
|
816
|
+
return "";
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// src/SelectProducts/EditableCell.tsx
|
|
820
|
+
import { jsx as jsx12, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
821
|
+
var STATUS_COLORS = {
|
|
822
|
+
active: "bg-emerald-100 text-emerald-800 border-emerald-200",
|
|
823
|
+
inactive: "bg-gray-100 text-gray-600 border-gray-200",
|
|
824
|
+
draft: "bg-amber-100 text-amber-800 border-amber-200",
|
|
825
|
+
new: "bg-sky-100 text-sky-800 border-sky-200",
|
|
826
|
+
problem: "bg-red-100 text-red-800 border-red-200",
|
|
827
|
+
"under review": "bg-violet-100 text-violet-800 border-violet-200"
|
|
828
|
+
};
|
|
829
|
+
function EditableCell({
|
|
830
|
+
value,
|
|
831
|
+
cellType,
|
|
832
|
+
fieldName,
|
|
833
|
+
allowedValues,
|
|
834
|
+
onSave,
|
|
835
|
+
readOnly = false
|
|
836
|
+
}) {
|
|
837
|
+
const displayValue = cellType === "timestamp" ? formatTimestamp(value) : String(value ?? "");
|
|
838
|
+
if (cellType === "timestamp") {
|
|
839
|
+
return /* @__PURE__ */ jsx12("span", { className: "text-sm text-muted-foreground", children: displayValue });
|
|
840
|
+
}
|
|
841
|
+
const isStatusField = fieldName === "status";
|
|
842
|
+
if (readOnly) {
|
|
843
|
+
if (isStatusField && displayValue) {
|
|
844
|
+
return /* @__PURE__ */ jsx12(
|
|
845
|
+
Badge,
|
|
846
|
+
{
|
|
847
|
+
variant: "outline",
|
|
848
|
+
className: cn("text-xs font-medium", STATUS_COLORS[displayValue] ?? ""),
|
|
849
|
+
children: displayValue
|
|
850
|
+
}
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
return /* @__PURE__ */ jsx12("span", { className: "text-sm", children: displayValue });
|
|
854
|
+
}
|
|
855
|
+
if (allowedValues && allowedValues.length > 0) {
|
|
856
|
+
return /* @__PURE__ */ jsx12(
|
|
857
|
+
DropdownEditor,
|
|
858
|
+
{
|
|
859
|
+
value: displayValue,
|
|
860
|
+
options: allowedValues,
|
|
861
|
+
onSave,
|
|
862
|
+
isStatus: isStatusField
|
|
863
|
+
}
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
return /* @__PURE__ */ jsx12(TextEditor, { value: displayValue, onSave });
|
|
867
|
+
}
|
|
868
|
+
function DropdownEditor({
|
|
869
|
+
value,
|
|
870
|
+
options,
|
|
871
|
+
onSave,
|
|
872
|
+
isStatus
|
|
873
|
+
}) {
|
|
874
|
+
const [open, setOpen] = useState2(false);
|
|
875
|
+
const [saving, setSaving] = useState2(false);
|
|
876
|
+
const handleSelect = useCallback2(
|
|
877
|
+
async (selected) => {
|
|
878
|
+
if (selected === value) {
|
|
879
|
+
setOpen(false);
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
setSaving(true);
|
|
883
|
+
try {
|
|
884
|
+
await onSave(selected);
|
|
885
|
+
} finally {
|
|
886
|
+
setSaving(false);
|
|
887
|
+
setOpen(false);
|
|
888
|
+
}
|
|
889
|
+
},
|
|
890
|
+
[onSave, value]
|
|
891
|
+
);
|
|
892
|
+
const displayNode = isStatus ? /* @__PURE__ */ jsx12(
|
|
893
|
+
Badge,
|
|
894
|
+
{
|
|
895
|
+
variant: "outline",
|
|
896
|
+
className: cn("text-xs font-medium", STATUS_COLORS[value] ?? ""),
|
|
897
|
+
children: value || "\u2014"
|
|
898
|
+
}
|
|
899
|
+
) : /* @__PURE__ */ jsx12("span", { className: "truncate text-sm", children: value || "\u2014" });
|
|
900
|
+
return /* @__PURE__ */ jsxs4(Popover, { open, onOpenChange: setOpen, children: [
|
|
901
|
+
/* @__PURE__ */ jsxs4(
|
|
902
|
+
PopoverTrigger,
|
|
903
|
+
{
|
|
904
|
+
disabled: saving,
|
|
905
|
+
className: buttonVariants({
|
|
906
|
+
variant: "ghost",
|
|
907
|
+
size: "sm",
|
|
908
|
+
className: "group h-auto w-full justify-between gap-1 px-2 py-1 font-normal"
|
|
909
|
+
}),
|
|
910
|
+
children: [
|
|
911
|
+
saving ? /* @__PURE__ */ jsx12(Loader2, { className: "size-3.5 animate-spin" }) : displayNode,
|
|
912
|
+
/* @__PURE__ */ jsx12(ChevronsUpDown, { className: "size-3 shrink-0 opacity-0 transition-opacity group-hover:opacity-50" })
|
|
913
|
+
]
|
|
914
|
+
}
|
|
915
|
+
),
|
|
916
|
+
/* @__PURE__ */ jsx12(PopoverContent, { className: "w-[220px] p-0", align: "start", children: /* @__PURE__ */ jsxs4(Command, { className: "[&_[data-slot=command-input-wrapper]]:h-8 [&_[data-slot=command-input]]:h-7 [&_[data-slot=command-input]]:py-1 [&_[data-slot=command-input]]:pl-1", children: [
|
|
917
|
+
/* @__PURE__ */ jsx12(CommandInput, { placeholder: `Search ${options.length} values...` }),
|
|
918
|
+
/* @__PURE__ */ jsxs4(CommandList, { children: [
|
|
919
|
+
/* @__PURE__ */ jsx12(CommandEmpty, { children: "No match found." }),
|
|
920
|
+
/* @__PURE__ */ jsx12(CommandGroup, { children: options.map((option) => /* @__PURE__ */ jsxs4(
|
|
921
|
+
CommandItem,
|
|
922
|
+
{
|
|
923
|
+
value: option,
|
|
924
|
+
onSelect: () => handleSelect(option),
|
|
925
|
+
children: [
|
|
926
|
+
isStatus ? /* @__PURE__ */ jsx12(
|
|
927
|
+
Badge,
|
|
928
|
+
{
|
|
929
|
+
variant: "outline",
|
|
930
|
+
className: cn(
|
|
931
|
+
"text-xs",
|
|
932
|
+
STATUS_COLORS[option] ?? ""
|
|
933
|
+
),
|
|
934
|
+
children: option
|
|
935
|
+
}
|
|
936
|
+
) : option,
|
|
937
|
+
/* @__PURE__ */ jsx12(
|
|
938
|
+
Check,
|
|
939
|
+
{
|
|
940
|
+
className: cn(
|
|
941
|
+
"ml-auto size-3.5",
|
|
942
|
+
value === option ? "opacity-100" : "opacity-0"
|
|
943
|
+
)
|
|
944
|
+
}
|
|
945
|
+
)
|
|
946
|
+
]
|
|
947
|
+
},
|
|
948
|
+
option
|
|
949
|
+
)) })
|
|
950
|
+
] })
|
|
951
|
+
] }) })
|
|
952
|
+
] });
|
|
953
|
+
}
|
|
954
|
+
function TextEditor({
|
|
955
|
+
value,
|
|
956
|
+
onSave
|
|
957
|
+
}) {
|
|
958
|
+
const [editing, setEditing] = useState2(false);
|
|
959
|
+
const [draft, setDraft] = useState2(value);
|
|
960
|
+
const [saving, setSaving] = useState2(false);
|
|
961
|
+
const inputRef = useRef2(null);
|
|
962
|
+
useEffect(() => {
|
|
963
|
+
setDraft(value);
|
|
964
|
+
}, [value]);
|
|
965
|
+
useEffect(() => {
|
|
966
|
+
if (editing) {
|
|
967
|
+
inputRef.current?.focus();
|
|
968
|
+
inputRef.current?.select();
|
|
969
|
+
}
|
|
970
|
+
}, [editing]);
|
|
971
|
+
const commit = useCallback2(async () => {
|
|
972
|
+
const trimmed = draft.trim();
|
|
973
|
+
if (trimmed === value) {
|
|
974
|
+
setEditing(false);
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
setSaving(true);
|
|
978
|
+
try {
|
|
979
|
+
await onSave(trimmed);
|
|
980
|
+
setEditing(false);
|
|
981
|
+
} catch {
|
|
982
|
+
setDraft(value);
|
|
983
|
+
setEditing(false);
|
|
984
|
+
} finally {
|
|
985
|
+
setSaving(false);
|
|
986
|
+
}
|
|
987
|
+
}, [draft, value, onSave]);
|
|
988
|
+
if (editing) {
|
|
989
|
+
return /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1", children: [
|
|
990
|
+
/* @__PURE__ */ jsx12(
|
|
991
|
+
Input,
|
|
992
|
+
{
|
|
993
|
+
ref: inputRef,
|
|
994
|
+
value: draft,
|
|
995
|
+
onChange: (e) => setDraft(e.target.value),
|
|
996
|
+
onBlur: commit,
|
|
997
|
+
onKeyDown: (e) => {
|
|
998
|
+
if (e.key === "Enter") commit();
|
|
999
|
+
if (e.key === "Escape") {
|
|
1000
|
+
setDraft(value);
|
|
1001
|
+
setEditing(false);
|
|
1002
|
+
}
|
|
1003
|
+
},
|
|
1004
|
+
disabled: saving,
|
|
1005
|
+
className: "h-7 text-sm"
|
|
1006
|
+
}
|
|
1007
|
+
),
|
|
1008
|
+
saving && /* @__PURE__ */ jsx12(Loader2, { className: "size-3.5 animate-spin text-muted-foreground" })
|
|
1009
|
+
] });
|
|
1010
|
+
}
|
|
1011
|
+
return /* @__PURE__ */ jsxs4(
|
|
1012
|
+
"button",
|
|
1013
|
+
{
|
|
1014
|
+
type: "button",
|
|
1015
|
+
className: "group flex w-full items-center gap-1.5 rounded px-1 py-0.5 text-left text-sm hover:bg-accent",
|
|
1016
|
+
onClick: () => setEditing(true),
|
|
23
1017
|
children: [
|
|
24
|
-
/* @__PURE__ */
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
1018
|
+
/* @__PURE__ */ jsx12("span", { className: "flex-1 truncate", children: value || "\u2014" }),
|
|
1019
|
+
/* @__PURE__ */ jsx12(Pencil, { className: "size-3 shrink-0 text-muted-foreground opacity-0 transition-opacity group-hover:opacity-100" })
|
|
1020
|
+
]
|
|
1021
|
+
}
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// src/SelectProducts/ProductTable.tsx
|
|
1026
|
+
import { jsx as jsx13, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1027
|
+
function ProductTable({
|
|
1028
|
+
products,
|
|
1029
|
+
columns: appColumns,
|
|
1030
|
+
allowedValues,
|
|
1031
|
+
loading,
|
|
1032
|
+
columnVisibility,
|
|
1033
|
+
onColumnVisibilityChange,
|
|
1034
|
+
rowSelection,
|
|
1035
|
+
onRowSelectionChange,
|
|
1036
|
+
onCellEdit,
|
|
1037
|
+
currentPage,
|
|
1038
|
+
totalPages,
|
|
1039
|
+
totalProducts,
|
|
1040
|
+
totalSkus,
|
|
1041
|
+
onPageChange,
|
|
1042
|
+
globalFilter
|
|
1043
|
+
}) {
|
|
1044
|
+
const tanstackColumns = useMemo(() => {
|
|
1045
|
+
const selectCol = {
|
|
1046
|
+
id: "_select",
|
|
1047
|
+
header: ({ table: table2 }) => /* @__PURE__ */ jsx13(
|
|
1048
|
+
Checkbox,
|
|
1049
|
+
{
|
|
1050
|
+
checked: table2.getIsAllPageRowsSelected() || table2.getIsSomePageRowsSelected() && "indeterminate",
|
|
1051
|
+
onCheckedChange: (checked) => table2.toggleAllPageRowsSelected(!!checked),
|
|
1052
|
+
"aria-label": "Select all",
|
|
1053
|
+
className: "translate-y-[2px]"
|
|
1054
|
+
}
|
|
1055
|
+
),
|
|
1056
|
+
cell: ({ row }) => /* @__PURE__ */ jsx13(
|
|
1057
|
+
Checkbox,
|
|
1058
|
+
{
|
|
1059
|
+
checked: row.getIsSelected(),
|
|
1060
|
+
onCheckedChange: (checked) => row.toggleSelected(!!checked),
|
|
1061
|
+
"aria-label": "Select row",
|
|
1062
|
+
className: "translate-y-[2px]"
|
|
1063
|
+
}
|
|
1064
|
+
),
|
|
1065
|
+
size: 40,
|
|
1066
|
+
enableHiding: false
|
|
1067
|
+
};
|
|
1068
|
+
const dataCols = appColumns.map(
|
|
1069
|
+
(col) => ({
|
|
1070
|
+
id: col.value,
|
|
1071
|
+
accessorFn: (row) => getCellValue(row, col.value, col.channel),
|
|
1072
|
+
header: () => col.name,
|
|
1073
|
+
cell: ({ row: tableRow }) => {
|
|
1074
|
+
const rowData = tableRow.original;
|
|
1075
|
+
const rawValue = getCellValue(rowData, col.value, col.channel);
|
|
1076
|
+
const template = rowData.template || "";
|
|
1077
|
+
const cellType = getCellType(col.value, col.channel, allowedValues, template);
|
|
1078
|
+
if (cellType === "image") {
|
|
1079
|
+
return /* @__PURE__ */ jsx13(ImageCell, { src: rawValue });
|
|
1080
|
+
}
|
|
1081
|
+
if (col.value === "Style Code") {
|
|
1082
|
+
const count = rowData._variantCount;
|
|
1083
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-1.5", children: [
|
|
1084
|
+
/* @__PURE__ */ jsx13("span", { className: "truncate text-sm", children: rawValue || "\u2014" }),
|
|
1085
|
+
count != null && count > 1 && /* @__PURE__ */ jsxs5(Badge, { variant: "secondary", className: "shrink-0 px-1.5 py-0 text-[10px] font-normal", children: [
|
|
1086
|
+
count,
|
|
1087
|
+
" SKUs"
|
|
1088
|
+
] })
|
|
1089
|
+
] });
|
|
1090
|
+
}
|
|
1091
|
+
const dropdownVals = getDropdownValues(
|
|
1092
|
+
allowedValues,
|
|
1093
|
+
template,
|
|
1094
|
+
col.value,
|
|
1095
|
+
col.channel
|
|
1096
|
+
);
|
|
1097
|
+
return /* @__PURE__ */ jsx13(
|
|
1098
|
+
EditableCell,
|
|
1099
|
+
{
|
|
1100
|
+
value: rawValue,
|
|
1101
|
+
cellType,
|
|
1102
|
+
fieldName: col.value,
|
|
1103
|
+
allowedValues: dropdownVals,
|
|
1104
|
+
onSave: async (newValue) => {
|
|
1105
|
+
await onCellEdit(rowData, col.value, col.channel, newValue);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
);
|
|
1109
|
+
},
|
|
1110
|
+
size: isImageColumn(col) ? 80 : 180,
|
|
1111
|
+
enableHiding: true
|
|
1112
|
+
})
|
|
1113
|
+
);
|
|
1114
|
+
return [selectCol, ...dataCols];
|
|
1115
|
+
}, [appColumns, allowedValues, onCellEdit]);
|
|
1116
|
+
const filteredProducts = useMemo(() => {
|
|
1117
|
+
if (!globalFilter.trim()) return products;
|
|
1118
|
+
const q = globalFilter.toLowerCase();
|
|
1119
|
+
return products.filter((row) => {
|
|
1120
|
+
for (const col of appColumns) {
|
|
1121
|
+
const val = getCellValue(row, col.value, col.channel);
|
|
1122
|
+
if (val && String(val).toLowerCase().includes(q)) return true;
|
|
1123
|
+
}
|
|
1124
|
+
return false;
|
|
1125
|
+
});
|
|
1126
|
+
}, [products, globalFilter, appColumns]);
|
|
1127
|
+
const table = useReactTable({
|
|
1128
|
+
data: filteredProducts,
|
|
1129
|
+
columns: tanstackColumns,
|
|
1130
|
+
state: {
|
|
1131
|
+
columnVisibility,
|
|
1132
|
+
rowSelection
|
|
1133
|
+
},
|
|
1134
|
+
onColumnVisibilityChange,
|
|
1135
|
+
onRowSelectionChange,
|
|
1136
|
+
getCoreRowModel: getCoreRowModel(),
|
|
1137
|
+
getRowId: (row) => row.id,
|
|
1138
|
+
enableRowSelection: true
|
|
1139
|
+
});
|
|
1140
|
+
const scrollRef = useRef3(null);
|
|
1141
|
+
const [canScrollRight, setCanScrollRight] = useState3(false);
|
|
1142
|
+
const [canScrollDown, setCanScrollDown] = useState3(false);
|
|
1143
|
+
const updateScrollFade = useCallback3(() => {
|
|
1144
|
+
const el = scrollRef.current;
|
|
1145
|
+
if (!el) return;
|
|
1146
|
+
const THRESHOLD = 4;
|
|
1147
|
+
setCanScrollRight(el.scrollWidth - el.scrollLeft - el.clientWidth > THRESHOLD);
|
|
1148
|
+
setCanScrollDown(el.scrollHeight - el.scrollTop - el.clientHeight > THRESHOLD);
|
|
1149
|
+
}, []);
|
|
1150
|
+
useEffect2(() => {
|
|
1151
|
+
const el = scrollRef.current;
|
|
1152
|
+
if (!el) return;
|
|
1153
|
+
updateScrollFade();
|
|
1154
|
+
el.addEventListener("scroll", updateScrollFade, { passive: true });
|
|
1155
|
+
const ro = new ResizeObserver(updateScrollFade);
|
|
1156
|
+
ro.observe(el);
|
|
1157
|
+
return () => {
|
|
1158
|
+
el.removeEventListener("scroll", updateScrollFade);
|
|
1159
|
+
ro.disconnect();
|
|
1160
|
+
};
|
|
1161
|
+
}, [updateScrollFade, filteredProducts]);
|
|
1162
|
+
if (loading) {
|
|
1163
|
+
return /* @__PURE__ */ jsx13("div", { className: "space-y-3 p-4", children: Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsx13(Skeleton, { className: "h-14 w-full rounded-md" }, i)) });
|
|
1164
|
+
}
|
|
1165
|
+
return /* @__PURE__ */ jsxs5("div", { className: "flex h-full flex-col", children: [
|
|
1166
|
+
/* @__PURE__ */ jsxs5("div", { className: "relative flex-1 overflow-hidden rounded-md border", children: [
|
|
1167
|
+
/* @__PURE__ */ jsx13(
|
|
1168
|
+
"div",
|
|
1169
|
+
{
|
|
1170
|
+
ref: scrollRef,
|
|
1171
|
+
className: "h-full overflow-auto",
|
|
1172
|
+
children: /* @__PURE__ */ jsxs5(Table, { children: [
|
|
1173
|
+
/* @__PURE__ */ jsx13(TableHeader, { className: "sticky top-0 z-10 bg-background", children: table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ jsx13(TableRow, { children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx13(
|
|
1174
|
+
TableHead,
|
|
1175
|
+
{
|
|
1176
|
+
style: { width: header.getSize() },
|
|
1177
|
+
className: header.id === "_select" ? "sticky left-0 z-20 bg-background" : "",
|
|
1178
|
+
children: header.isPlaceholder ? null : flexRender(
|
|
1179
|
+
header.column.columnDef.header,
|
|
1180
|
+
header.getContext()
|
|
1181
|
+
)
|
|
1182
|
+
},
|
|
1183
|
+
header.id
|
|
1184
|
+
)) }, headerGroup.id)) }),
|
|
1185
|
+
/* @__PURE__ */ jsx13(TableBody, { children: table.getRowModel().rows.length > 0 ? table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx13(
|
|
1186
|
+
TableRow,
|
|
1187
|
+
{
|
|
1188
|
+
"data-state": row.getIsSelected() ? "selected" : void 0,
|
|
1189
|
+
children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx13(
|
|
1190
|
+
TableCell,
|
|
1191
|
+
{
|
|
1192
|
+
style: { width: cell.column.getSize() },
|
|
1193
|
+
className: cell.column.id === "_select" ? "sticky left-0 z-10 bg-background" : "",
|
|
1194
|
+
children: flexRender(
|
|
1195
|
+
cell.column.columnDef.cell,
|
|
1196
|
+
cell.getContext()
|
|
1197
|
+
)
|
|
1198
|
+
},
|
|
1199
|
+
cell.id
|
|
1200
|
+
))
|
|
1201
|
+
},
|
|
1202
|
+
row.id
|
|
1203
|
+
)) : /* @__PURE__ */ jsx13(TableRow, { children: /* @__PURE__ */ jsx13(
|
|
1204
|
+
TableCell,
|
|
1205
|
+
{
|
|
1206
|
+
colSpan: tanstackColumns.length,
|
|
1207
|
+
className: "h-32 text-center text-muted-foreground",
|
|
1208
|
+
children: globalFilter ? "No products match your search." : "No products found."
|
|
1209
|
+
}
|
|
1210
|
+
) }) })
|
|
1211
|
+
] })
|
|
1212
|
+
}
|
|
1213
|
+
),
|
|
1214
|
+
/* @__PURE__ */ jsx13(
|
|
1215
|
+
"div",
|
|
1216
|
+
{
|
|
1217
|
+
className: `pointer-events-none absolute right-0 top-0 z-20 h-full w-10 transition-opacity duration-200 ${canScrollRight ? "opacity-100" : "opacity-0"}`,
|
|
1218
|
+
style: {
|
|
1219
|
+
background: "linear-gradient(to left, rgba(0,0,0,0.08) 0%, rgba(0,0,0,0.03) 40%, transparent 100%)"
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
),
|
|
1223
|
+
/* @__PURE__ */ jsx13(
|
|
1224
|
+
"div",
|
|
1225
|
+
{
|
|
1226
|
+
className: `pointer-events-none absolute bottom-0 left-0 z-20 h-10 w-full transition-opacity duration-200 ${canScrollDown ? "opacity-100" : "opacity-0"}`,
|
|
1227
|
+
style: {
|
|
1228
|
+
background: "linear-gradient(to top, rgba(0,0,0,0.08) 0%, rgba(0,0,0,0.03) 40%, transparent 100%)"
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
)
|
|
1232
|
+
] }),
|
|
1233
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between border-t px-3 py-2", children: [
|
|
1234
|
+
/* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
|
|
1235
|
+
totalProducts > 0 && /* @__PURE__ */ jsxs5("span", { className: "flex items-center gap-1", children: [
|
|
1236
|
+
/* @__PURE__ */ jsx13(Package, { className: "size-3.5" }),
|
|
1237
|
+
totalProducts.toLocaleString(),
|
|
1238
|
+
" ",
|
|
1239
|
+
totalProducts === 1 ? "style" : "styles"
|
|
28
1240
|
] }),
|
|
29
|
-
/* @__PURE__ */
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
1241
|
+
totalSkus > 0 && /* @__PURE__ */ jsxs5("span", { className: "flex items-center gap-1", children: [
|
|
1242
|
+
/* @__PURE__ */ jsx13(Layers, { className: "size-3.5" }),
|
|
1243
|
+
totalSkus.toLocaleString(),
|
|
1244
|
+
" ",
|
|
1245
|
+
totalSkus === 1 ? "SKU" : "SKUs"
|
|
1246
|
+
] }),
|
|
1247
|
+
table.getFilteredSelectedRowModel().rows.length > 0 && /* @__PURE__ */ jsxs5("span", { className: "font-medium text-foreground", children: [
|
|
1248
|
+
"\xB7 ",
|
|
1249
|
+
table.getFilteredSelectedRowModel().rows.length,
|
|
1250
|
+
" selected"
|
|
33
1251
|
] })
|
|
1252
|
+
] }),
|
|
1253
|
+
totalPages > 0 && /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", children: [
|
|
1254
|
+
/* @__PURE__ */ jsxs5(
|
|
1255
|
+
Button,
|
|
1256
|
+
{
|
|
1257
|
+
variant: "outline",
|
|
1258
|
+
size: "sm",
|
|
1259
|
+
onClick: () => onPageChange(currentPage - 1),
|
|
1260
|
+
disabled: currentPage <= 1,
|
|
1261
|
+
className: "h-7 px-2",
|
|
1262
|
+
children: [
|
|
1263
|
+
/* @__PURE__ */ jsx13(ChevronLeft, { className: "size-4" }),
|
|
1264
|
+
"Prev"
|
|
1265
|
+
]
|
|
1266
|
+
}
|
|
1267
|
+
),
|
|
1268
|
+
/* @__PURE__ */ jsxs5("span", { className: "min-w-[5rem] text-center text-xs text-muted-foreground", children: [
|
|
1269
|
+
"Page ",
|
|
1270
|
+
currentPage,
|
|
1271
|
+
" of ",
|
|
1272
|
+
totalPages
|
|
1273
|
+
] }),
|
|
1274
|
+
/* @__PURE__ */ jsxs5(
|
|
1275
|
+
Button,
|
|
1276
|
+
{
|
|
1277
|
+
variant: "outline",
|
|
1278
|
+
size: "sm",
|
|
1279
|
+
onClick: () => onPageChange(currentPage + 1),
|
|
1280
|
+
disabled: currentPage >= totalPages,
|
|
1281
|
+
className: "h-7 px-2",
|
|
1282
|
+
children: [
|
|
1283
|
+
"Next",
|
|
1284
|
+
/* @__PURE__ */ jsx13(ChevronRight, { className: "size-4" })
|
|
1285
|
+
]
|
|
1286
|
+
}
|
|
1287
|
+
)
|
|
1288
|
+
] })
|
|
1289
|
+
] })
|
|
1290
|
+
] });
|
|
1291
|
+
}
|
|
1292
|
+
function isImageColumn(col) {
|
|
1293
|
+
const lower = col.value.toLowerCase();
|
|
1294
|
+
return lower.includes("image") && !lower.includes("image_count");
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// src/SelectProducts/ColumnManager.tsx
|
|
1298
|
+
import { useMemo as useMemo2, useState as useState4 } from "react";
|
|
1299
|
+
import { Columns, Loader2 as Loader22, Search } from "lucide-react";
|
|
1300
|
+
|
|
1301
|
+
// src/ui/switch.tsx
|
|
1302
|
+
import * as SwitchPrimitive from "@radix-ui/react-switch";
|
|
1303
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
1304
|
+
function Switch({
|
|
1305
|
+
className,
|
|
1306
|
+
...props
|
|
1307
|
+
}) {
|
|
1308
|
+
return /* @__PURE__ */ jsx14(
|
|
1309
|
+
SwitchPrimitive.Root,
|
|
1310
|
+
{
|
|
1311
|
+
"data-slot": "switch",
|
|
1312
|
+
className: cn(
|
|
1313
|
+
"peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-switch-background focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
|
1314
|
+
className
|
|
1315
|
+
),
|
|
1316
|
+
...props,
|
|
1317
|
+
children: /* @__PURE__ */ jsx14(
|
|
1318
|
+
SwitchPrimitive.Thumb,
|
|
1319
|
+
{
|
|
1320
|
+
"data-slot": "switch-thumb",
|
|
1321
|
+
className: cn(
|
|
1322
|
+
"bg-card dark:data-[state=unchecked]:bg-card-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
|
|
1323
|
+
)
|
|
1324
|
+
}
|
|
1325
|
+
)
|
|
1326
|
+
}
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// src/SelectProducts/ColumnManager.tsx
|
|
1331
|
+
import { jsx as jsx15, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1332
|
+
function ColumnManager({
|
|
1333
|
+
allColumns,
|
|
1334
|
+
columnVisibility,
|
|
1335
|
+
onToggle,
|
|
1336
|
+
loading
|
|
1337
|
+
}) {
|
|
1338
|
+
const [search, setSearch] = useState4("");
|
|
1339
|
+
const [open, setOpen] = useState4(false);
|
|
1340
|
+
const filtered = useMemo2(() => {
|
|
1341
|
+
if (!search.trim()) return allColumns;
|
|
1342
|
+
const q = search.toLowerCase();
|
|
1343
|
+
return allColumns.filter(
|
|
1344
|
+
(col) => col.name.toLowerCase().includes(q) || col.value.toLowerCase().includes(q) || col.channel.toLowerCase().includes(q)
|
|
1345
|
+
);
|
|
1346
|
+
}, [allColumns, search]);
|
|
1347
|
+
const grouped = useMemo2(() => {
|
|
1348
|
+
const map = {};
|
|
1349
|
+
for (const col of filtered) {
|
|
1350
|
+
const ch = col.channel || "other";
|
|
1351
|
+
if (!map[ch]) map[ch] = [];
|
|
1352
|
+
map[ch].push(col);
|
|
1353
|
+
}
|
|
1354
|
+
return map;
|
|
1355
|
+
}, [filtered]);
|
|
1356
|
+
const visibleCount = useMemo2(
|
|
1357
|
+
() => allColumns.filter((col) => columnVisibility[col.value] !== false).length,
|
|
1358
|
+
[allColumns, columnVisibility]
|
|
1359
|
+
);
|
|
1360
|
+
return /* @__PURE__ */ jsxs6(Popover, { open, onOpenChange: setOpen, children: [
|
|
1361
|
+
/* @__PURE__ */ jsxs6(
|
|
1362
|
+
PopoverTrigger,
|
|
1363
|
+
{
|
|
1364
|
+
className: buttonVariants({
|
|
1365
|
+
variant: "outline",
|
|
1366
|
+
size: "sm",
|
|
1367
|
+
className: "gap-1.5"
|
|
1368
|
+
}),
|
|
1369
|
+
children: [
|
|
1370
|
+
/* @__PURE__ */ jsx15(Columns, { className: "size-4" }),
|
|
1371
|
+
"Columns",
|
|
1372
|
+
loading ? /* @__PURE__ */ jsx15(Loader22, { className: "ml-0.5 size-3.5 animate-spin text-muted-foreground" }) : /* @__PURE__ */ jsxs6(Badge, { variant: "secondary", className: "ml-0.5 text-xs font-normal", children: [
|
|
1373
|
+
visibleCount,
|
|
1374
|
+
"/",
|
|
1375
|
+
allColumns.length
|
|
1376
|
+
] })
|
|
1377
|
+
]
|
|
1378
|
+
}
|
|
1379
|
+
),
|
|
1380
|
+
/* @__PURE__ */ jsxs6(
|
|
1381
|
+
PopoverContent,
|
|
1382
|
+
{
|
|
1383
|
+
className: "w-[300px] p-0",
|
|
1384
|
+
align: "end",
|
|
1385
|
+
sideOffset: 4,
|
|
1386
|
+
collisionPadding: 8,
|
|
1387
|
+
children: [
|
|
1388
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2 border-b px-3 py-2", children: [
|
|
1389
|
+
/* @__PURE__ */ jsx15(Search, { className: "size-3.5 shrink-0 text-muted-foreground" }),
|
|
1390
|
+
/* @__PURE__ */ jsx15(
|
|
1391
|
+
"input",
|
|
1392
|
+
{
|
|
1393
|
+
placeholder: "Search columns...",
|
|
1394
|
+
value: search,
|
|
1395
|
+
onChange: (e) => setSearch(e.target.value),
|
|
1396
|
+
className: "h-6 w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground"
|
|
1397
|
+
}
|
|
1398
|
+
)
|
|
1399
|
+
] }),
|
|
1400
|
+
/* @__PURE__ */ jsxs6("div", { className: "max-h-[360px] overflow-y-auto overscroll-contain p-2", children: [
|
|
1401
|
+
Object.entries(grouped).map(([channel, cols]) => /* @__PURE__ */ jsxs6("div", { className: "mb-2 last:mb-0", children: [
|
|
1402
|
+
/* @__PURE__ */ jsxs6("div", { className: "sticky top-0 z-10 mb-1 bg-popover px-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground", children: [
|
|
1403
|
+
channel,
|
|
1404
|
+
/* @__PURE__ */ jsxs6("span", { className: "ml-1 font-normal", children: [
|
|
1405
|
+
"(",
|
|
1406
|
+
cols.length,
|
|
1407
|
+
")"
|
|
1408
|
+
] })
|
|
1409
|
+
] }),
|
|
1410
|
+
cols.map((col) => {
|
|
1411
|
+
const isVisible = columnVisibility[col.value] !== false;
|
|
1412
|
+
return /* @__PURE__ */ jsxs6(
|
|
1413
|
+
"label",
|
|
1414
|
+
{
|
|
1415
|
+
className: "flex cursor-pointer items-center justify-between gap-2 rounded-md px-2 py-1.5 text-sm hover:bg-accent",
|
|
1416
|
+
children: [
|
|
1417
|
+
/* @__PURE__ */ jsx15("span", { className: "min-w-0 truncate", children: col.name }),
|
|
1418
|
+
/* @__PURE__ */ jsx15(
|
|
1419
|
+
Switch,
|
|
1420
|
+
{
|
|
1421
|
+
checked: isVisible,
|
|
1422
|
+
onCheckedChange: () => onToggle(col.value),
|
|
1423
|
+
className: "shrink-0 scale-75"
|
|
1424
|
+
}
|
|
1425
|
+
)
|
|
1426
|
+
]
|
|
1427
|
+
},
|
|
1428
|
+
col.value
|
|
1429
|
+
);
|
|
1430
|
+
})
|
|
1431
|
+
] }, channel)),
|
|
1432
|
+
filtered.length === 0 && /* @__PURE__ */ jsx15("p", { className: "py-4 text-center text-sm text-muted-foreground", children: "No columns match your search." })
|
|
1433
|
+
] })
|
|
1434
|
+
]
|
|
1435
|
+
}
|
|
1436
|
+
)
|
|
1437
|
+
] });
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// src/SelectProducts/api.ts
|
|
1441
|
+
async function fetchRequiredFields(storeId) {
|
|
1442
|
+
const { catalogixBaseUrl } = getApiConfig();
|
|
1443
|
+
const url = `${catalogixBaseUrl}/api/v1/store/${storeId}/product/required`;
|
|
1444
|
+
return fetchJson(url);
|
|
1445
|
+
}
|
|
1446
|
+
async function searchProducts(params) {
|
|
1447
|
+
const { catalogixBaseUrl } = getApiConfig();
|
|
1448
|
+
const url = `${catalogixBaseUrl}/api/v1/store/${params.storeId}/products/search?response_as_list=true&include_result_count=true`;
|
|
1449
|
+
return fetchJson(url, {
|
|
1450
|
+
method: "POST",
|
|
1451
|
+
headers: { "Content-Type": "application/json" },
|
|
1452
|
+
body: JSON.stringify({
|
|
1453
|
+
filters: params.filters,
|
|
1454
|
+
fields: params.fields,
|
|
1455
|
+
start: params.start,
|
|
1456
|
+
rows: params.rows,
|
|
1457
|
+
group: true
|
|
1458
|
+
})
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
async function updateProduct({
|
|
1462
|
+
storeId,
|
|
1463
|
+
productCode,
|
|
1464
|
+
productUuid,
|
|
1465
|
+
channel,
|
|
1466
|
+
fieldName,
|
|
1467
|
+
value,
|
|
1468
|
+
userId
|
|
1469
|
+
}) {
|
|
1470
|
+
const { keplerProxyPrefix } = getApiConfig();
|
|
1471
|
+
const metadata = encodeURIComponent(
|
|
1472
|
+
JSON.stringify({ catalogix_user_id: userId })
|
|
1473
|
+
);
|
|
1474
|
+
const url = `${keplerProxyPrefix}/v1/store/${storeId}/products/update?req_metadata=${metadata}`;
|
|
1475
|
+
const body = {
|
|
1476
|
+
[productCode]: {
|
|
1477
|
+
[channel]: {
|
|
1478
|
+
product_uuid: productUuid,
|
|
1479
|
+
[fieldName]: value
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
};
|
|
1483
|
+
return fetchJson(url, {
|
|
1484
|
+
method: "POST",
|
|
1485
|
+
headers: { "Content-Type": "application/json" },
|
|
1486
|
+
body: JSON.stringify(body)
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
// src/SelectProducts/index.tsx
|
|
1491
|
+
import { Fragment as Fragment2, jsx as jsx16, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1492
|
+
function SelectProducts({
|
|
1493
|
+
workspaceId,
|
|
1494
|
+
storeId,
|
|
1495
|
+
userId,
|
|
1496
|
+
submitted,
|
|
1497
|
+
submittedValues,
|
|
1498
|
+
onConfirmSelection,
|
|
1499
|
+
useProductUuid = true
|
|
1500
|
+
}) {
|
|
1501
|
+
const [loading, setLoading] = useState5(false);
|
|
1502
|
+
const [products, setProducts] = useState5([]);
|
|
1503
|
+
const [allColumns, setAllColumns] = useState5(DEFAULT_COLUMNS);
|
|
1504
|
+
const [allowedValues, setAllowedValues] = useState5({});
|
|
1505
|
+
const [pageCount, setPageCount] = useState5(0);
|
|
1506
|
+
const [currentPage, setCurrentPage] = useState5(1);
|
|
1507
|
+
const [totalProducts, setTotalProducts] = useState5(0);
|
|
1508
|
+
const [totalSkus, setTotalSkus] = useState5(0);
|
|
1509
|
+
const [globalFilter, setGlobalFilter] = useState5("");
|
|
1510
|
+
const [columnVisibility, setColumnVisibility] = useState5({});
|
|
1511
|
+
const [rowSelection, setRowSelection] = useState5({});
|
|
1512
|
+
const [columnsReady, setColumnsReady] = useState5(false);
|
|
1513
|
+
const [isSubmitting, setIsSubmitting] = useState5(false);
|
|
1514
|
+
const [isSubmitted, setIsSubmitted] = useState5(Boolean(submitted));
|
|
1515
|
+
const [localSubmittedValues, setLocalSubmittedValues] = useState5(
|
|
1516
|
+
submittedValues ?? null
|
|
1517
|
+
);
|
|
1518
|
+
const rowSelectionRef = useRef4({});
|
|
1519
|
+
useEffect3(() => {
|
|
1520
|
+
rowSelectionRef.current = rowSelection;
|
|
1521
|
+
}, [rowSelection]);
|
|
1522
|
+
const requiredFieldsFetchedRef = useRef4(null);
|
|
1523
|
+
useEffect3(() => {
|
|
1524
|
+
setIsSubmitted(Boolean(submitted));
|
|
1525
|
+
setLocalSubmittedValues(submittedValues ?? null);
|
|
1526
|
+
}, [submitted, submittedValues]);
|
|
1527
|
+
useEffect3(() => {
|
|
1528
|
+
if (!storeId || requiredFieldsFetchedRef.current === storeId) return;
|
|
1529
|
+
requiredFieldsFetchedRef.current = storeId;
|
|
1530
|
+
(async () => {
|
|
1531
|
+
try {
|
|
1532
|
+
const response = await fetchRequiredFields(storeId);
|
|
1533
|
+
if (response?.status?.code !== 0 || !response.data) return;
|
|
1534
|
+
const { columns: reqCols, allowedValues: reqAv } = buildFromRequiredFields(response.data);
|
|
1535
|
+
const existingValues = new Set(DEFAULT_COLUMNS.map((c) => c.value));
|
|
1536
|
+
const newCols = reqCols.filter((c) => !existingValues.has(c.value));
|
|
1537
|
+
if (newCols.length > 0) {
|
|
1538
|
+
setAllColumns((prev) => {
|
|
1539
|
+
const prevValues = new Set(prev.map((c) => c.value));
|
|
1540
|
+
const toAdd = newCols.filter((c) => !prevValues.has(c.value));
|
|
1541
|
+
return toAdd.length > 0 ? [...prev, ...toAdd] : prev;
|
|
1542
|
+
});
|
|
1543
|
+
setColumnVisibility((prev) => {
|
|
1544
|
+
const next = { ...prev };
|
|
1545
|
+
for (const col of newCols) {
|
|
1546
|
+
if (!CORE_COLUMN_VALUES.has(col.value) && next[col.value] === void 0) {
|
|
1547
|
+
next[col.value] = false;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
return next;
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
setAllowedValues((prev) => mergeAllowedValues(prev, reqAv));
|
|
1554
|
+
} catch (error) {
|
|
1555
|
+
console.error("Failed to fetch required fields:", error);
|
|
1556
|
+
} finally {
|
|
1557
|
+
setColumnsReady(true);
|
|
1558
|
+
}
|
|
1559
|
+
})();
|
|
1560
|
+
}, [storeId]);
|
|
1561
|
+
const fetchProducts = useCallback4(
|
|
1562
|
+
async (page = 1) => {
|
|
1563
|
+
if (!storeId) return;
|
|
1564
|
+
const fields = buildFieldsFromColumns(allColumns, DEFAULT_SOURCE_MP);
|
|
1565
|
+
const filters = buildFiltersFromFields(fields);
|
|
1566
|
+
const offset = (page - 1) * PRODUCTS_PER_PAGE;
|
|
1567
|
+
setLoading(true);
|
|
1568
|
+
try {
|
|
1569
|
+
const response = await searchProducts({
|
|
1570
|
+
storeId,
|
|
1571
|
+
fields,
|
|
1572
|
+
filters,
|
|
1573
|
+
start: offset,
|
|
1574
|
+
rows: PRODUCTS_PER_PAGE
|
|
1575
|
+
});
|
|
1576
|
+
if (response?.status?.code === 0) {
|
|
1577
|
+
const groups = Array.isArray(response.data) ? response.data : [];
|
|
1578
|
+
const allRows = formatProducts(groups);
|
|
1579
|
+
setProducts(formatProductsByStyle(allRows));
|
|
1580
|
+
const av = response.allowed_values || response.allowedValues;
|
|
1581
|
+
if (av) {
|
|
1582
|
+
setAllowedValues((prev) => mergeAllowedValues(prev, av));
|
|
1583
|
+
}
|
|
1584
|
+
const stats = response.stats || {};
|
|
1585
|
+
const docCount = Math.max(stats.doc_count ?? 0, 0);
|
|
1586
|
+
const variantCount = Math.max(stats.variant_count ?? 0, 0);
|
|
1587
|
+
const apiPages = Math.max(stats.pages ?? 0, 0);
|
|
1588
|
+
setTotalProducts(docCount);
|
|
1589
|
+
setTotalSkus(variantCount);
|
|
1590
|
+
setPageCount(
|
|
1591
|
+
apiPages > 0 ? apiPages : docCount > 0 ? Math.ceil(docCount / PRODUCTS_PER_PAGE) : 0
|
|
1592
|
+
);
|
|
1593
|
+
} else {
|
|
1594
|
+
setProducts([]);
|
|
1595
|
+
}
|
|
1596
|
+
} catch (error) {
|
|
1597
|
+
console.error("Failed to fetch products:", error);
|
|
1598
|
+
setProducts([]);
|
|
1599
|
+
} finally {
|
|
1600
|
+
setLoading(false);
|
|
1601
|
+
}
|
|
1602
|
+
},
|
|
1603
|
+
[storeId, allColumns]
|
|
1604
|
+
);
|
|
1605
|
+
useEffect3(() => {
|
|
1606
|
+
if (storeId) {
|
|
1607
|
+
fetchProducts(currentPage);
|
|
1608
|
+
}
|
|
1609
|
+
}, [storeId, currentPage, fetchProducts]);
|
|
1610
|
+
const handleCellEdit = useCallback4(
|
|
1611
|
+
async (row, fieldName, channel, newValue) => {
|
|
1612
|
+
const apiChannel = channel === "required" ? "product" : channel;
|
|
1613
|
+
const productCode = row.product_code;
|
|
1614
|
+
const productUuid = row.product_uuid;
|
|
1615
|
+
if (!productCode || !productUuid) {
|
|
1616
|
+
throw new Error("Missing product code or UUID");
|
|
1617
|
+
}
|
|
1618
|
+
await updateProduct({
|
|
1619
|
+
storeId,
|
|
1620
|
+
productCode,
|
|
1621
|
+
productUuid,
|
|
1622
|
+
channel: apiChannel,
|
|
1623
|
+
fieldName,
|
|
1624
|
+
value: newValue,
|
|
1625
|
+
userId: userId || ""
|
|
1626
|
+
});
|
|
1627
|
+
setProducts(
|
|
1628
|
+
(prev) => prev.map((p) => {
|
|
1629
|
+
if (p.id !== row.id) return p;
|
|
1630
|
+
const namespacedKey = `${fieldName}::${apiChannel}`;
|
|
1631
|
+
return { ...p, [namespacedKey]: newValue, ...fieldName === "status" ? { status: newValue } : {} };
|
|
1632
|
+
})
|
|
1633
|
+
);
|
|
1634
|
+
},
|
|
1635
|
+
[storeId, userId]
|
|
1636
|
+
);
|
|
1637
|
+
const handlePageChange = useCallback4((page) => {
|
|
1638
|
+
setCurrentPage(page);
|
|
1639
|
+
setRowSelection({});
|
|
1640
|
+
}, []);
|
|
1641
|
+
const selectedProducts = useMemo3(() => {
|
|
1642
|
+
const selectedIds = Object.keys(rowSelection).filter(
|
|
1643
|
+
(k) => rowSelection[k]
|
|
1644
|
+
);
|
|
1645
|
+
return products.filter((p) => selectedIds.includes(p.id));
|
|
1646
|
+
}, [rowSelection, products]);
|
|
1647
|
+
const hasSelection = selectedProducts.length > 0;
|
|
1648
|
+
const handleConfirmSelection = useCallback4(async () => {
|
|
1649
|
+
if (!onConfirmSelection) return;
|
|
1650
|
+
const productUuids = Array.from(
|
|
1651
|
+
new Set(selectedProducts.map((p) => p.product_uuid).filter(Boolean))
|
|
1652
|
+
);
|
|
1653
|
+
const productCodes = Array.from(
|
|
1654
|
+
new Set(selectedProducts.map((p) => p.product_code).filter(Boolean))
|
|
1655
|
+
);
|
|
1656
|
+
const selectionPayload = useProductUuid ? { product_uuids: productUuids } : { product_codes: productCodes };
|
|
1657
|
+
const resumeValues = {
|
|
1658
|
+
product_uuids: productUuids,
|
|
1659
|
+
product_codes: productCodes,
|
|
1660
|
+
workspaceId,
|
|
1661
|
+
storeId
|
|
1662
|
+
};
|
|
1663
|
+
setIsSubmitting(true);
|
|
1664
|
+
try {
|
|
1665
|
+
const result = await onConfirmSelection(
|
|
1666
|
+
selectedProducts,
|
|
1667
|
+
[],
|
|
1668
|
+
// excludedProducts
|
|
1669
|
+
[],
|
|
1670
|
+
// selectedPageRange
|
|
1671
|
+
{},
|
|
1672
|
+
// filters
|
|
1673
|
+
useProductUuid,
|
|
1674
|
+
selectionPayload,
|
|
1675
|
+
resumeValues
|
|
1676
|
+
);
|
|
1677
|
+
if (result?.submitted) {
|
|
1678
|
+
setIsSubmitted(true);
|
|
1679
|
+
setLocalSubmittedValues(result.submittedValues ?? resumeValues);
|
|
1680
|
+
}
|
|
1681
|
+
} catch (error) {
|
|
1682
|
+
console.error("Failed to submit selection:", error);
|
|
1683
|
+
} finally {
|
|
1684
|
+
setIsSubmitting(false);
|
|
1685
|
+
}
|
|
1686
|
+
}, [
|
|
1687
|
+
onConfirmSelection,
|
|
1688
|
+
selectedProducts,
|
|
1689
|
+
useProductUuid,
|
|
1690
|
+
workspaceId,
|
|
1691
|
+
storeId
|
|
1692
|
+
]);
|
|
1693
|
+
const handleColumnToggle = useCallback4((columnValue) => {
|
|
1694
|
+
setColumnVisibility((prev) => ({
|
|
1695
|
+
...prev,
|
|
1696
|
+
[columnValue]: prev[columnValue] === false ? true : false
|
|
1697
|
+
}));
|
|
1698
|
+
}, []);
|
|
1699
|
+
if (isSubmitted) {
|
|
1700
|
+
return /* @__PURE__ */ jsxs7(Card, { className: "p-4", children: [
|
|
1701
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2 font-semibold text-emerald-700", children: [
|
|
1702
|
+
/* @__PURE__ */ jsx16(CheckCircle, { className: "size-4" }),
|
|
1703
|
+
"Selection submitted"
|
|
1704
|
+
] }),
|
|
1705
|
+
localSubmittedValues && /* @__PURE__ */ jsxs7("details", { className: "mt-2", children: [
|
|
1706
|
+
/* @__PURE__ */ jsx16("summary", { className: "cursor-pointer text-sm text-muted-foreground", children: "View submitted data" }),
|
|
1707
|
+
/* @__PURE__ */ jsx16("pre", { className: "mt-2 max-h-40 overflow-auto rounded bg-muted p-2 text-xs", children: JSON.stringify(localSubmittedValues, null, 2) })
|
|
1708
|
+
] })
|
|
1709
|
+
] });
|
|
1710
|
+
}
|
|
1711
|
+
if (!storeId || !workspaceId) {
|
|
1712
|
+
return /* @__PURE__ */ jsx16(Card, { className: "p-6 text-center text-muted-foreground", children: "Store context missing. Please start from a store selection step." });
|
|
1713
|
+
}
|
|
1714
|
+
if (!loading && products.length === 0) {
|
|
1715
|
+
return /* @__PURE__ */ jsx16(Card, { className: "p-6 text-center text-muted-foreground", children: "No products found for this store." });
|
|
1716
|
+
}
|
|
1717
|
+
return /* @__PURE__ */ jsxs7("div", { className: "flex h-[580px] flex-col gap-3 rounded-lg border border-border bg-card p-3 shadow-sm", children: [
|
|
1718
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
|
|
1719
|
+
/* @__PURE__ */ jsxs7("div", { className: "relative flex-1", children: [
|
|
1720
|
+
/* @__PURE__ */ jsx16(Search2, { className: "absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" }),
|
|
1721
|
+
/* @__PURE__ */ jsx16(
|
|
1722
|
+
Input,
|
|
1723
|
+
{
|
|
1724
|
+
placeholder: "Search products...",
|
|
1725
|
+
value: globalFilter,
|
|
1726
|
+
onChange: (e) => setGlobalFilter(e.target.value),
|
|
1727
|
+
className: "h-9 pl-9"
|
|
1728
|
+
}
|
|
1729
|
+
)
|
|
1730
|
+
] }),
|
|
1731
|
+
/* @__PURE__ */ jsx16(
|
|
1732
|
+
ColumnManager,
|
|
1733
|
+
{
|
|
1734
|
+
allColumns,
|
|
1735
|
+
columnVisibility,
|
|
1736
|
+
onToggle: handleColumnToggle,
|
|
1737
|
+
loading: !columnsReady
|
|
1738
|
+
}
|
|
1739
|
+
)
|
|
1740
|
+
] }),
|
|
1741
|
+
/* @__PURE__ */ jsx16("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx16(
|
|
1742
|
+
ProductTable,
|
|
1743
|
+
{
|
|
1744
|
+
products,
|
|
1745
|
+
columns: allColumns,
|
|
1746
|
+
allowedValues,
|
|
1747
|
+
loading,
|
|
1748
|
+
columnVisibility,
|
|
1749
|
+
onColumnVisibilityChange: setColumnVisibility,
|
|
1750
|
+
rowSelection,
|
|
1751
|
+
onRowSelectionChange: setRowSelection,
|
|
1752
|
+
onCellEdit: handleCellEdit,
|
|
1753
|
+
currentPage,
|
|
1754
|
+
totalPages: pageCount,
|
|
1755
|
+
totalProducts,
|
|
1756
|
+
totalSkus,
|
|
1757
|
+
onPageChange: handlePageChange,
|
|
1758
|
+
globalFilter
|
|
1759
|
+
}
|
|
1760
|
+
) }),
|
|
1761
|
+
hasSelection && /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between rounded-lg border bg-muted/50 px-4 py-2", children: [
|
|
1762
|
+
/* @__PURE__ */ jsxs7("span", { className: "text-sm font-medium", children: [
|
|
1763
|
+
selectedProducts.length,
|
|
1764
|
+
" product",
|
|
1765
|
+
selectedProducts.length !== 1 ? "s" : "",
|
|
1766
|
+
" selected"
|
|
1767
|
+
] }),
|
|
1768
|
+
/* @__PURE__ */ jsx16(
|
|
1769
|
+
Button,
|
|
1770
|
+
{
|
|
1771
|
+
onClick: handleConfirmSelection,
|
|
1772
|
+
disabled: isSubmitting,
|
|
1773
|
+
size: "sm",
|
|
1774
|
+
children: isSubmitting ? /* @__PURE__ */ jsxs7(Fragment2, { children: [
|
|
1775
|
+
/* @__PURE__ */ jsx16(Loader23, { className: "size-4 animate-spin" }),
|
|
1776
|
+
"Submitting..."
|
|
1777
|
+
] }) : "Confirm selection"
|
|
1778
|
+
}
|
|
1779
|
+
)
|
|
1780
|
+
] })
|
|
1781
|
+
] });
|
|
1782
|
+
}
|
|
1783
|
+
function mergeAllowedValues(base, incoming) {
|
|
1784
|
+
const result = { ...base };
|
|
1785
|
+
for (const [channel, channelData] of Object.entries(incoming)) {
|
|
1786
|
+
if (!result[channel]) {
|
|
1787
|
+
result[channel] = channelData;
|
|
1788
|
+
} else {
|
|
1789
|
+
result[channel] = { ...result[channel] };
|
|
1790
|
+
for (const [key, value] of Object.entries(channelData)) {
|
|
1791
|
+
if (!result[channel][key]) {
|
|
1792
|
+
result[channel][key] = value;
|
|
1793
|
+
} else {
|
|
1794
|
+
result[channel][key] = { ...result[channel][key], ...value };
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
return result;
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
// src/CreateStore/index.tsx
|
|
1803
|
+
import { useState as useState6, useEffect as useEffect4, useMemo as useMemo4 } from "react";
|
|
1804
|
+
import { Loader2 as Loader24, ChevronDown, ArrowLeft, Info } from "lucide-react";
|
|
1805
|
+
|
|
1806
|
+
// src/ui/label.tsx
|
|
1807
|
+
import * as LabelPrimitive from "@radix-ui/react-label";
|
|
1808
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
1809
|
+
function Label({
|
|
1810
|
+
className,
|
|
1811
|
+
...props
|
|
1812
|
+
}) {
|
|
1813
|
+
return /* @__PURE__ */ jsx17(
|
|
1814
|
+
LabelPrimitive.Root,
|
|
1815
|
+
{
|
|
1816
|
+
"data-slot": "label",
|
|
1817
|
+
className: cn(
|
|
1818
|
+
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
1819
|
+
className
|
|
1820
|
+
),
|
|
1821
|
+
...props
|
|
1822
|
+
}
|
|
1823
|
+
);
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
// src/ui/select.tsx
|
|
1827
|
+
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
1828
|
+
import {
|
|
1829
|
+
CheckIcon as CheckIcon2,
|
|
1830
|
+
ChevronDownIcon,
|
|
1831
|
+
ChevronUpIcon
|
|
1832
|
+
} from "lucide-react";
|
|
1833
|
+
import { jsx as jsx18, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1834
|
+
function Select({
|
|
1835
|
+
...props
|
|
1836
|
+
}) {
|
|
1837
|
+
return /* @__PURE__ */ jsx18(SelectPrimitive.Root, { "data-slot": "select", ...props });
|
|
1838
|
+
}
|
|
1839
|
+
function SelectValue({
|
|
1840
|
+
...props
|
|
1841
|
+
}) {
|
|
1842
|
+
return /* @__PURE__ */ jsx18(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
|
|
1843
|
+
}
|
|
1844
|
+
function SelectTrigger({
|
|
1845
|
+
className,
|
|
1846
|
+
size = "default",
|
|
1847
|
+
children,
|
|
1848
|
+
...props
|
|
1849
|
+
}) {
|
|
1850
|
+
return /* @__PURE__ */ jsxs8(
|
|
1851
|
+
SelectPrimitive.Trigger,
|
|
1852
|
+
{
|
|
1853
|
+
"data-slot": "select-trigger",
|
|
1854
|
+
"data-size": size,
|
|
1855
|
+
className: cn(
|
|
1856
|
+
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-full items-center justify-between gap-2 rounded-md border bg-input-background px-3 py-2 text-sm whitespace-nowrap transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
1857
|
+
className
|
|
1858
|
+
),
|
|
1859
|
+
...props,
|
|
1860
|
+
children: [
|
|
1861
|
+
children,
|
|
1862
|
+
/* @__PURE__ */ jsx18(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx18(ChevronDownIcon, { className: "size-4 opacity-50" }) })
|
|
1863
|
+
]
|
|
1864
|
+
}
|
|
1865
|
+
);
|
|
1866
|
+
}
|
|
1867
|
+
function SelectContent({
|
|
1868
|
+
className,
|
|
1869
|
+
children,
|
|
1870
|
+
position = "popper",
|
|
1871
|
+
...props
|
|
1872
|
+
}) {
|
|
1873
|
+
return /* @__PURE__ */ jsx18(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs8(
|
|
1874
|
+
SelectPrimitive.Content,
|
|
1875
|
+
{
|
|
1876
|
+
"data-slot": "select-content",
|
|
1877
|
+
className: cn(
|
|
1878
|
+
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
|
1879
|
+
position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
1880
|
+
className
|
|
1881
|
+
),
|
|
1882
|
+
position,
|
|
1883
|
+
...props,
|
|
1884
|
+
children: [
|
|
1885
|
+
/* @__PURE__ */ jsx18(SelectScrollUpButton, {}),
|
|
1886
|
+
/* @__PURE__ */ jsx18(
|
|
1887
|
+
SelectPrimitive.Viewport,
|
|
1888
|
+
{
|
|
1889
|
+
className: cn(
|
|
1890
|
+
"p-1",
|
|
1891
|
+
position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1"
|
|
1892
|
+
),
|
|
1893
|
+
children
|
|
1894
|
+
}
|
|
1895
|
+
),
|
|
1896
|
+
/* @__PURE__ */ jsx18(SelectScrollDownButton, {})
|
|
1897
|
+
]
|
|
1898
|
+
}
|
|
1899
|
+
) });
|
|
1900
|
+
}
|
|
1901
|
+
function SelectItem({
|
|
1902
|
+
className,
|
|
1903
|
+
children,
|
|
1904
|
+
...props
|
|
1905
|
+
}) {
|
|
1906
|
+
return /* @__PURE__ */ jsxs8(
|
|
1907
|
+
SelectPrimitive.Item,
|
|
1908
|
+
{
|
|
1909
|
+
"data-slot": "select-item",
|
|
1910
|
+
className: cn(
|
|
1911
|
+
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
|
1912
|
+
className
|
|
1913
|
+
),
|
|
1914
|
+
...props,
|
|
1915
|
+
children: [
|
|
1916
|
+
/* @__PURE__ */ jsx18("span", { className: "absolute right-2 flex size-3.5 items-center justify-center", children: /* @__PURE__ */ jsx18(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx18(CheckIcon2, { className: "size-4" }) }) }),
|
|
1917
|
+
/* @__PURE__ */ jsx18(SelectPrimitive.ItemText, { children })
|
|
34
1918
|
]
|
|
35
1919
|
}
|
|
36
1920
|
);
|
|
37
1921
|
}
|
|
1922
|
+
function SelectScrollUpButton({
|
|
1923
|
+
className,
|
|
1924
|
+
...props
|
|
1925
|
+
}) {
|
|
1926
|
+
return /* @__PURE__ */ jsx18(
|
|
1927
|
+
SelectPrimitive.ScrollUpButton,
|
|
1928
|
+
{
|
|
1929
|
+
"data-slot": "select-scroll-up-button",
|
|
1930
|
+
className: cn(
|
|
1931
|
+
"flex cursor-default items-center justify-center py-1",
|
|
1932
|
+
className
|
|
1933
|
+
),
|
|
1934
|
+
...props,
|
|
1935
|
+
children: /* @__PURE__ */ jsx18(ChevronUpIcon, { className: "size-4" })
|
|
1936
|
+
}
|
|
1937
|
+
);
|
|
1938
|
+
}
|
|
1939
|
+
function SelectScrollDownButton({
|
|
1940
|
+
className,
|
|
1941
|
+
...props
|
|
1942
|
+
}) {
|
|
1943
|
+
return /* @__PURE__ */ jsx18(
|
|
1944
|
+
SelectPrimitive.ScrollDownButton,
|
|
1945
|
+
{
|
|
1946
|
+
"data-slot": "select-scroll-down-button",
|
|
1947
|
+
className: cn(
|
|
1948
|
+
"flex cursor-default items-center justify-center py-1",
|
|
1949
|
+
className
|
|
1950
|
+
),
|
|
1951
|
+
...props,
|
|
1952
|
+
children: /* @__PURE__ */ jsx18(ChevronDownIcon, { className: "size-4" })
|
|
1953
|
+
}
|
|
1954
|
+
);
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
// src/ui/separator.tsx
|
|
1958
|
+
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
|
1959
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
1960
|
+
function Separator2({
|
|
1961
|
+
className,
|
|
1962
|
+
orientation = "horizontal",
|
|
1963
|
+
decorative = true,
|
|
1964
|
+
...props
|
|
1965
|
+
}) {
|
|
1966
|
+
return /* @__PURE__ */ jsx19(
|
|
1967
|
+
SeparatorPrimitive.Root,
|
|
1968
|
+
{
|
|
1969
|
+
"data-slot": "separator-root",
|
|
1970
|
+
decorative,
|
|
1971
|
+
orientation,
|
|
1972
|
+
className: cn(
|
|
1973
|
+
"bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
|
|
1974
|
+
className
|
|
1975
|
+
),
|
|
1976
|
+
...props
|
|
1977
|
+
}
|
|
1978
|
+
);
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
// src/CreateStore/api.ts
|
|
1982
|
+
async function getStoreValues() {
|
|
1983
|
+
const { catalogixBaseUrl } = getApiConfig();
|
|
1984
|
+
return fetchJson(`${catalogixBaseUrl}/api/v1/storeValues`);
|
|
1985
|
+
}
|
|
1986
|
+
async function getMarketplaces(region, workspaceId) {
|
|
1987
|
+
const { catalogixBaseUrl } = getApiConfig();
|
|
1988
|
+
return fetchJson(
|
|
1989
|
+
`${catalogixBaseUrl}/api/v1/marketplaces?region=${encodeURIComponent(region)}&workspace=${encodeURIComponent(workspaceId)}`
|
|
1990
|
+
);
|
|
1991
|
+
}
|
|
1992
|
+
async function createStore(payload) {
|
|
1993
|
+
const { catalogixBaseUrl } = getApiConfig();
|
|
1994
|
+
return fetchJson(`${catalogixBaseUrl}/api/v1/store`, {
|
|
1995
|
+
method: "POST",
|
|
1996
|
+
headers: { "Content-Type": "application/json" },
|
|
1997
|
+
body: JSON.stringify(payload)
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
async function getStore(workspaceId, storeId) {
|
|
2001
|
+
const { catalogixBaseUrl } = getApiConfig();
|
|
2002
|
+
return fetchJson(
|
|
2003
|
+
`${catalogixBaseUrl}/api/v1/workspace/${encodeURIComponent(workspaceId)}/store/${encodeURIComponent(storeId)}`
|
|
2004
|
+
);
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
// src/CreateStore/index.tsx
|
|
2008
|
+
import { Fragment as Fragment3, jsx as jsx20, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2009
|
+
var REGIONS = [
|
|
2010
|
+
"India",
|
|
2011
|
+
"Middle East (UAE)",
|
|
2012
|
+
"South East Asia",
|
|
2013
|
+
"North America",
|
|
2014
|
+
"Europe",
|
|
2015
|
+
"UK",
|
|
2016
|
+
"Australia",
|
|
2017
|
+
"Japan",
|
|
2018
|
+
"South America",
|
|
2019
|
+
"Africa"
|
|
2020
|
+
];
|
|
2021
|
+
var WEBSITE_REGEX = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/;
|
|
2022
|
+
function CreateStore({
|
|
2023
|
+
workspaceId,
|
|
2024
|
+
stores = [],
|
|
2025
|
+
initialStoreName = "",
|
|
2026
|
+
useCatalogixTaxonomy = false,
|
|
2027
|
+
shouldRefetchStore = false,
|
|
2028
|
+
onStoreCreated,
|
|
2029
|
+
onCreateError,
|
|
2030
|
+
onClose
|
|
2031
|
+
}) {
|
|
2032
|
+
const [storeName, setStoreName] = useState6(initialStoreName);
|
|
2033
|
+
const [storeType, setStoreType] = useState6([]);
|
|
2034
|
+
const [country, setCountry] = useState6("India");
|
|
2035
|
+
const [channels, setChannels] = useState6([]);
|
|
2036
|
+
const [storeWebsite, setStoreWebsite] = useState6("");
|
|
2037
|
+
const [languages, setLanguages] = useState6([]);
|
|
2038
|
+
const [linkedStoreId, setLinkedStoreId] = useState6(null);
|
|
2039
|
+
const [showAdvanced, setShowAdvanced] = useState6(false);
|
|
2040
|
+
const [loading, setLoading] = useState6(false);
|
|
2041
|
+
const [fetchingValues, setFetchingValues] = useState6(true);
|
|
2042
|
+
const [fetchingChannels, setFetchingChannels] = useState6(false);
|
|
2043
|
+
const [error, setError] = useState6(null);
|
|
2044
|
+
const [storeTypes, setStoreTypes] = useState6([]);
|
|
2045
|
+
const [availableLanguages, setAvailableLanguages] = useState6([]);
|
|
2046
|
+
const [channelOptions, setChannelOptions] = useState6([]);
|
|
2047
|
+
const [showConnectInfo, setShowConnectInfo] = useState6(false);
|
|
2048
|
+
const [channelPopoverOpen, setChannelPopoverOpen] = useState6(false);
|
|
2049
|
+
const [languagePopoverOpen, setLanguagePopoverOpen] = useState6(false);
|
|
2050
|
+
useEffect4(() => {
|
|
2051
|
+
let active = true;
|
|
2052
|
+
getStoreValues().then((res) => {
|
|
2053
|
+
if (!active || !res.data) return;
|
|
2054
|
+
if (res.data.product_sale_type) {
|
|
2055
|
+
setStoreTypes(res.data.product_sale_type);
|
|
2056
|
+
const mp = res.data.product_sale_type.find((t) => t.id === "MP");
|
|
2057
|
+
if (mp) setStoreType(["MP"]);
|
|
2058
|
+
}
|
|
2059
|
+
if (res.data.language) {
|
|
2060
|
+
setAvailableLanguages(res.data.language);
|
|
2061
|
+
setLanguages([res.data.language[0]]);
|
|
2062
|
+
}
|
|
2063
|
+
}).catch(console.error).finally(() => active && setFetchingValues(false));
|
|
2064
|
+
return () => {
|
|
2065
|
+
active = false;
|
|
2066
|
+
};
|
|
2067
|
+
}, []);
|
|
2068
|
+
useEffect4(() => {
|
|
2069
|
+
if (!workspaceId || !country) return;
|
|
2070
|
+
let active = true;
|
|
2071
|
+
setFetchingChannels(true);
|
|
2072
|
+
getMarketplaces(country, workspaceId).then((res) => {
|
|
2073
|
+
if (!active) return;
|
|
2074
|
+
if (res.data && Array.isArray(res.data)) {
|
|
2075
|
+
setChannelOptions(
|
|
2076
|
+
res.data.map((ch) => ({
|
|
2077
|
+
value: ch,
|
|
2078
|
+
label: ch.replace(/_/g, " "),
|
|
2079
|
+
thumbnailUrl: res.details?.[ch.toLowerCase()]?.thumbnail_url
|
|
2080
|
+
})).sort((a, b) => a.label.localeCompare(b.label))
|
|
2081
|
+
);
|
|
2082
|
+
} else {
|
|
2083
|
+
setChannelOptions([]);
|
|
2084
|
+
}
|
|
2085
|
+
}).catch(() => setChannelOptions([])).finally(() => active && setFetchingChannels(false));
|
|
2086
|
+
return () => {
|
|
2087
|
+
active = false;
|
|
2088
|
+
};
|
|
2089
|
+
}, [country, workspaceId]);
|
|
2090
|
+
const hasMP = storeType.includes("MP");
|
|
2091
|
+
const hasD2C = storeType.includes("D2C");
|
|
2092
|
+
const isCreateDisabled = useMemo4(() => {
|
|
2093
|
+
if (!storeName.trim() || !country || storeType.length === 0) return true;
|
|
2094
|
+
if (hasMP && channels.length === 0) return true;
|
|
2095
|
+
if (hasD2C && !storeWebsite.trim()) return true;
|
|
2096
|
+
return false;
|
|
2097
|
+
}, [storeName, country, storeType, channels, storeWebsite, hasMP, hasD2C]);
|
|
2098
|
+
const handleCreate = async () => {
|
|
2099
|
+
if (hasD2C) {
|
|
2100
|
+
const trimmed = storeWebsite.trim();
|
|
2101
|
+
if (!trimmed || !WEBSITE_REGEX.test(trimmed)) {
|
|
2102
|
+
setError("Invalid store website. Use https://www.example.com");
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
setLoading(true);
|
|
2107
|
+
setError(null);
|
|
2108
|
+
const channelList = channels.map((c) => c.value);
|
|
2109
|
+
if (hasD2C && !channelList.length) {
|
|
2110
|
+
channelList.push("SMP", "streamoid-4");
|
|
2111
|
+
}
|
|
2112
|
+
const payload = {
|
|
2113
|
+
storeName,
|
|
2114
|
+
storeType,
|
|
2115
|
+
country,
|
|
2116
|
+
channels: channelList,
|
|
2117
|
+
languages,
|
|
2118
|
+
storeWebsite,
|
|
2119
|
+
linkedStore: linkedStoreId ?? void 0,
|
|
2120
|
+
selectedWorkspaceId: workspaceId,
|
|
2121
|
+
...useCatalogixTaxonomy ? { storeTaxonomy: "catalogix" } : {}
|
|
2122
|
+
};
|
|
2123
|
+
try {
|
|
2124
|
+
const res = await createStore(payload);
|
|
2125
|
+
if (res.status?.code === -1) {
|
|
2126
|
+
setError(res.status.message || "Store creation failed");
|
|
2127
|
+
onCreateError?.(res);
|
|
2128
|
+
} else {
|
|
2129
|
+
let finalData = res.data || {};
|
|
2130
|
+
if (shouldRefetchStore && res.data?._id) {
|
|
2131
|
+
const refetched = await getStore(res.data.workspaceId, res.data._id);
|
|
2132
|
+
finalData = { ...finalData, ...refetched.data };
|
|
2133
|
+
}
|
|
2134
|
+
onStoreCreated?.(finalData);
|
|
2135
|
+
onClose?.();
|
|
2136
|
+
}
|
|
2137
|
+
} catch (err) {
|
|
2138
|
+
const msg = err instanceof Error ? err.message : "Something went wrong";
|
|
2139
|
+
setError(msg);
|
|
2140
|
+
onCreateError?.(err);
|
|
2141
|
+
} finally {
|
|
2142
|
+
setLoading(false);
|
|
2143
|
+
}
|
|
2144
|
+
};
|
|
2145
|
+
const toggleChannel = (ch) => {
|
|
2146
|
+
setChannels(
|
|
2147
|
+
(prev) => prev.some((c) => c.value === ch.value) ? prev.filter((c) => c.value !== ch.value) : [...prev, ch]
|
|
2148
|
+
);
|
|
2149
|
+
};
|
|
2150
|
+
const toggleLanguage = (lang) => {
|
|
2151
|
+
setLanguages(
|
|
2152
|
+
(prev) => prev.includes(lang) ? prev.filter((l) => l !== lang) : [...prev, lang]
|
|
2153
|
+
);
|
|
2154
|
+
};
|
|
2155
|
+
if (fetchingValues || fetchingChannels) {
|
|
2156
|
+
return /* @__PURE__ */ jsxs9("div", { className: "space-y-4 p-4", children: [
|
|
2157
|
+
/* @__PURE__ */ jsx20(Skeleton, { className: "h-8 w-48" }),
|
|
2158
|
+
/* @__PURE__ */ jsx20(Skeleton, { className: "h-10 w-full" }),
|
|
2159
|
+
/* @__PURE__ */ jsx20(Skeleton, { className: "h-10 w-full" }),
|
|
2160
|
+
/* @__PURE__ */ jsx20(Skeleton, { className: "h-10 w-full" })
|
|
2161
|
+
] });
|
|
2162
|
+
}
|
|
2163
|
+
return /* @__PURE__ */ jsxs9("div", { className: "flex flex-col gap-4 p-4", children: [
|
|
2164
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2", children: [
|
|
2165
|
+
onClose && /* @__PURE__ */ jsx20(Button, { variant: "ghost", size: "icon", onClick: onClose, children: /* @__PURE__ */ jsx20(ArrowLeft, { className: "size-4" }) }),
|
|
2166
|
+
/* @__PURE__ */ jsx20("h3", { className: "text-lg font-semibold", children: "Create Store" })
|
|
2167
|
+
] }),
|
|
2168
|
+
/* @__PURE__ */ jsx20(Separator2, {}),
|
|
2169
|
+
error && /* @__PURE__ */ jsx20("div", { className: "rounded-md border border-destructive/50 bg-destructive/10 px-3 py-2 text-sm text-destructive", children: error }),
|
|
2170
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-1.5", children: [
|
|
2171
|
+
/* @__PURE__ */ jsxs9(Label, { children: [
|
|
2172
|
+
"Store Name ",
|
|
2173
|
+
/* @__PURE__ */ jsx20("span", { className: "text-destructive", children: "*" })
|
|
2174
|
+
] }),
|
|
2175
|
+
/* @__PURE__ */ jsx20(
|
|
2176
|
+
Input,
|
|
2177
|
+
{
|
|
2178
|
+
placeholder: "My Store",
|
|
2179
|
+
value: storeName,
|
|
2180
|
+
onChange: (e) => setStoreName(e.target.value),
|
|
2181
|
+
autoFocus: true
|
|
2182
|
+
}
|
|
2183
|
+
)
|
|
2184
|
+
] }),
|
|
2185
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-1.5", children: [
|
|
2186
|
+
/* @__PURE__ */ jsxs9(Label, { children: [
|
|
2187
|
+
"Region ",
|
|
2188
|
+
/* @__PURE__ */ jsx20("span", { className: "text-destructive", children: "*" })
|
|
2189
|
+
] }),
|
|
2190
|
+
/* @__PURE__ */ jsxs9(Select, { value: country, onValueChange: setCountry, children: [
|
|
2191
|
+
/* @__PURE__ */ jsx20(SelectTrigger, { children: /* @__PURE__ */ jsx20(SelectValue, { placeholder: "Select region" }) }),
|
|
2192
|
+
/* @__PURE__ */ jsx20(SelectContent, { children: REGIONS.map((r) => /* @__PURE__ */ jsx20(SelectItem, { value: r, children: r }, r)) })
|
|
2193
|
+
] })
|
|
2194
|
+
] }),
|
|
2195
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-1.5", children: [
|
|
2196
|
+
/* @__PURE__ */ jsxs9(Label, { children: [
|
|
2197
|
+
"Business ",
|
|
2198
|
+
/* @__PURE__ */ jsx20("span", { className: "text-destructive", children: "*" })
|
|
2199
|
+
] }),
|
|
2200
|
+
/* @__PURE__ */ jsx20("div", { className: "flex flex-wrap gap-2", children: storeTypes.map((t) => {
|
|
2201
|
+
const selected = storeType.includes(t.id);
|
|
2202
|
+
return /* @__PURE__ */ jsxs9(
|
|
2203
|
+
"button",
|
|
2204
|
+
{
|
|
2205
|
+
type: "button",
|
|
2206
|
+
onClick: () => {
|
|
2207
|
+
setStoreType(
|
|
2208
|
+
(prev) => prev.includes(t.id) ? prev.filter((x) => x !== t.id) : [...prev, t.id]
|
|
2209
|
+
);
|
|
2210
|
+
if (t.id === "MP" && storeType.includes("MP")) {
|
|
2211
|
+
setChannels([]);
|
|
2212
|
+
}
|
|
2213
|
+
},
|
|
2214
|
+
className: cn(
|
|
2215
|
+
"flex flex-col items-start gap-0.5 rounded-lg border px-3 py-2 text-left text-sm transition-colors",
|
|
2216
|
+
selected ? "border-primary bg-primary/10 text-primary" : "border-border hover:bg-accent"
|
|
2217
|
+
),
|
|
2218
|
+
children: [
|
|
2219
|
+
/* @__PURE__ */ jsx20("span", { className: "font-medium", children: t.label.replace("(MP)", "").replace("(D2C)", "").trim() }),
|
|
2220
|
+
/* @__PURE__ */ jsx20("span", { className: "text-xs text-muted-foreground", children: t.description })
|
|
2221
|
+
]
|
|
2222
|
+
},
|
|
2223
|
+
t.id
|
|
2224
|
+
);
|
|
2225
|
+
}) })
|
|
2226
|
+
] }),
|
|
2227
|
+
hasMP && /* @__PURE__ */ jsxs9("div", { className: "space-y-1.5", children: [
|
|
2228
|
+
/* @__PURE__ */ jsxs9(Label, { children: [
|
|
2229
|
+
"Channels ",
|
|
2230
|
+
/* @__PURE__ */ jsx20("span", { className: "text-destructive", children: "*" })
|
|
2231
|
+
] }),
|
|
2232
|
+
/* @__PURE__ */ jsxs9(Popover, { open: channelPopoverOpen, onOpenChange: setChannelPopoverOpen, children: [
|
|
2233
|
+
/* @__PURE__ */ jsx20(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs9(
|
|
2234
|
+
Button,
|
|
2235
|
+
{
|
|
2236
|
+
variant: "outline",
|
|
2237
|
+
className: "w-full justify-between font-normal",
|
|
2238
|
+
children: [
|
|
2239
|
+
channels.length > 0 ? /* @__PURE__ */ jsx20("div", { className: "flex flex-wrap gap-1", children: channels.map((c) => /* @__PURE__ */ jsx20(Badge, { variant: "secondary", className: "text-xs", children: c.label }, c.value)) }) : /* @__PURE__ */ jsx20("span", { className: "text-muted-foreground", children: "Select channels..." }),
|
|
2240
|
+
/* @__PURE__ */ jsx20(ChevronDown, { className: "ml-2 size-4 shrink-0 opacity-50" })
|
|
2241
|
+
]
|
|
2242
|
+
}
|
|
2243
|
+
) }),
|
|
2244
|
+
/* @__PURE__ */ jsx20(PopoverContent, { className: "w-[300px] p-0", align: "start", children: /* @__PURE__ */ jsxs9(Command, { children: [
|
|
2245
|
+
/* @__PURE__ */ jsx20(CommandInput, { placeholder: "Search channels..." }),
|
|
2246
|
+
/* @__PURE__ */ jsxs9(CommandList, { children: [
|
|
2247
|
+
/* @__PURE__ */ jsx20(CommandEmpty, { children: "No channels found." }),
|
|
2248
|
+
/* @__PURE__ */ jsx20(CommandGroup, { children: channelOptions.map((ch) => {
|
|
2249
|
+
const selected = channels.some((c) => c.value === ch.value);
|
|
2250
|
+
return /* @__PURE__ */ jsxs9(
|
|
2251
|
+
CommandItem,
|
|
2252
|
+
{
|
|
2253
|
+
value: ch.value,
|
|
2254
|
+
onSelect: () => toggleChannel(ch),
|
|
2255
|
+
children: [
|
|
2256
|
+
/* @__PURE__ */ jsx20(
|
|
2257
|
+
"div",
|
|
2258
|
+
{
|
|
2259
|
+
className: cn(
|
|
2260
|
+
"mr-2 flex size-4 items-center justify-center rounded border",
|
|
2261
|
+
selected ? "border-primary bg-primary text-primary-foreground" : "border-muted-foreground/40"
|
|
2262
|
+
),
|
|
2263
|
+
children: selected && /* @__PURE__ */ jsx20("span", { className: "text-xs", children: "\u2713" })
|
|
2264
|
+
}
|
|
2265
|
+
),
|
|
2266
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-2", children: [
|
|
2267
|
+
ch.thumbnailUrl && /* @__PURE__ */ jsx20(
|
|
2268
|
+
"img",
|
|
2269
|
+
{
|
|
2270
|
+
src: ch.thumbnailUrl,
|
|
2271
|
+
alt: "",
|
|
2272
|
+
className: "size-5 rounded object-cover"
|
|
2273
|
+
}
|
|
2274
|
+
),
|
|
2275
|
+
ch.label
|
|
2276
|
+
] })
|
|
2277
|
+
]
|
|
2278
|
+
},
|
|
2279
|
+
ch.value
|
|
2280
|
+
);
|
|
2281
|
+
}) })
|
|
2282
|
+
] })
|
|
2283
|
+
] }) })
|
|
2284
|
+
] })
|
|
2285
|
+
] }),
|
|
2286
|
+
hasD2C && /* @__PURE__ */ jsxs9("div", { className: "space-y-1.5", children: [
|
|
2287
|
+
/* @__PURE__ */ jsxs9(Label, { children: [
|
|
2288
|
+
"Store Website ",
|
|
2289
|
+
/* @__PURE__ */ jsx20("span", { className: "text-destructive", children: "*" })
|
|
2290
|
+
] }),
|
|
2291
|
+
/* @__PURE__ */ jsx20(
|
|
2292
|
+
Input,
|
|
2293
|
+
{
|
|
2294
|
+
placeholder: "https://www.example.com",
|
|
2295
|
+
value: storeWebsite,
|
|
2296
|
+
onChange: (e) => setStoreWebsite(e.target.value)
|
|
2297
|
+
}
|
|
2298
|
+
)
|
|
2299
|
+
] }),
|
|
2300
|
+
/* @__PURE__ */ jsxs9(
|
|
2301
|
+
"button",
|
|
2302
|
+
{
|
|
2303
|
+
type: "button",
|
|
2304
|
+
onClick: () => setShowAdvanced(!showAdvanced),
|
|
2305
|
+
className: "flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground",
|
|
2306
|
+
children: [
|
|
2307
|
+
"Advanced settings",
|
|
2308
|
+
/* @__PURE__ */ jsx20(
|
|
2309
|
+
ChevronDown,
|
|
2310
|
+
{
|
|
2311
|
+
className: cn(
|
|
2312
|
+
"size-4 transition-transform",
|
|
2313
|
+
showAdvanced && "rotate-180"
|
|
2314
|
+
)
|
|
2315
|
+
}
|
|
2316
|
+
)
|
|
2317
|
+
]
|
|
2318
|
+
}
|
|
2319
|
+
),
|
|
2320
|
+
showAdvanced && /* @__PURE__ */ jsxs9("div", { className: "space-y-4 rounded-lg border p-3", children: [
|
|
2321
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-1.5", children: [
|
|
2322
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1.5", children: [
|
|
2323
|
+
/* @__PURE__ */ jsx20(Label, { children: "Connect to an existing Store" }),
|
|
2324
|
+
/* @__PURE__ */ jsx20(
|
|
2325
|
+
"button",
|
|
2326
|
+
{
|
|
2327
|
+
type: "button",
|
|
2328
|
+
onClick: () => setShowConnectInfo(true),
|
|
2329
|
+
className: "text-muted-foreground hover:text-foreground",
|
|
2330
|
+
children: /* @__PURE__ */ jsx20(Info, { className: "size-3.5" })
|
|
2331
|
+
}
|
|
2332
|
+
)
|
|
2333
|
+
] }),
|
|
2334
|
+
/* @__PURE__ */ jsxs9(
|
|
2335
|
+
Select,
|
|
2336
|
+
{
|
|
2337
|
+
value: linkedStoreId || "",
|
|
2338
|
+
onValueChange: (v) => setLinkedStoreId(v || null),
|
|
2339
|
+
children: [
|
|
2340
|
+
/* @__PURE__ */ jsx20(SelectTrigger, { children: /* @__PURE__ */ jsx20(SelectValue, { placeholder: "None" }) }),
|
|
2341
|
+
/* @__PURE__ */ jsx20(SelectContent, { children: stores.map((s) => /* @__PURE__ */ jsx20(SelectItem, { value: s._id, children: s.name }, s._id)) })
|
|
2342
|
+
]
|
|
2343
|
+
}
|
|
2344
|
+
)
|
|
2345
|
+
] }),
|
|
2346
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-1.5", children: [
|
|
2347
|
+
/* @__PURE__ */ jsxs9(Label, { children: [
|
|
2348
|
+
"Language(s) ",
|
|
2349
|
+
/* @__PURE__ */ jsx20("span", { className: "text-destructive", children: "*" })
|
|
2350
|
+
] }),
|
|
2351
|
+
/* @__PURE__ */ jsxs9(Popover, { open: languagePopoverOpen, onOpenChange: setLanguagePopoverOpen, children: [
|
|
2352
|
+
/* @__PURE__ */ jsx20(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs9(
|
|
2353
|
+
Button,
|
|
2354
|
+
{
|
|
2355
|
+
variant: "outline",
|
|
2356
|
+
className: "w-full justify-between font-normal",
|
|
2357
|
+
children: [
|
|
2358
|
+
languages.length > 0 ? /* @__PURE__ */ jsx20("div", { className: "flex flex-wrap gap-1", children: languages.map((l) => /* @__PURE__ */ jsx20(Badge, { variant: "secondary", className: "text-xs", children: l }, l)) }) : /* @__PURE__ */ jsx20("span", { className: "text-muted-foreground", children: "Select languages..." }),
|
|
2359
|
+
/* @__PURE__ */ jsx20(ChevronDown, { className: "ml-2 size-4 shrink-0 opacity-50" })
|
|
2360
|
+
]
|
|
2361
|
+
}
|
|
2362
|
+
) }),
|
|
2363
|
+
/* @__PURE__ */ jsx20(PopoverContent, { className: "w-[250px] p-0", align: "start", children: /* @__PURE__ */ jsxs9(Command, { children: [
|
|
2364
|
+
/* @__PURE__ */ jsx20(CommandInput, { placeholder: "Search languages..." }),
|
|
2365
|
+
/* @__PURE__ */ jsxs9(CommandList, { children: [
|
|
2366
|
+
/* @__PURE__ */ jsx20(CommandEmpty, { children: "No languages found." }),
|
|
2367
|
+
/* @__PURE__ */ jsx20(CommandGroup, { children: availableLanguages.map((lang) => {
|
|
2368
|
+
const selected = languages.includes(lang);
|
|
2369
|
+
return /* @__PURE__ */ jsxs9(
|
|
2370
|
+
CommandItem,
|
|
2371
|
+
{
|
|
2372
|
+
value: lang,
|
|
2373
|
+
onSelect: () => toggleLanguage(lang),
|
|
2374
|
+
children: [
|
|
2375
|
+
/* @__PURE__ */ jsx20(
|
|
2376
|
+
"div",
|
|
2377
|
+
{
|
|
2378
|
+
className: cn(
|
|
2379
|
+
"mr-2 flex size-4 items-center justify-center rounded border",
|
|
2380
|
+
selected ? "border-primary bg-primary text-primary-foreground" : "border-muted-foreground/40"
|
|
2381
|
+
),
|
|
2382
|
+
children: selected && /* @__PURE__ */ jsx20("span", { className: "text-xs", children: "\u2713" })
|
|
2383
|
+
}
|
|
2384
|
+
),
|
|
2385
|
+
lang
|
|
2386
|
+
]
|
|
2387
|
+
},
|
|
2388
|
+
lang
|
|
2389
|
+
);
|
|
2390
|
+
}) })
|
|
2391
|
+
] })
|
|
2392
|
+
] }) })
|
|
2393
|
+
] })
|
|
2394
|
+
] })
|
|
2395
|
+
] }),
|
|
2396
|
+
/* @__PURE__ */ jsx20(Separator2, {}),
|
|
2397
|
+
/* @__PURE__ */ jsx20(
|
|
2398
|
+
Button,
|
|
2399
|
+
{
|
|
2400
|
+
onClick: handleCreate,
|
|
2401
|
+
disabled: isCreateDisabled || loading,
|
|
2402
|
+
className: "w-full",
|
|
2403
|
+
children: loading ? /* @__PURE__ */ jsxs9(Fragment3, { children: [
|
|
2404
|
+
/* @__PURE__ */ jsx20(Loader24, { className: "size-4 animate-spin" }),
|
|
2405
|
+
"Creating..."
|
|
2406
|
+
] }) : "Create & Proceed"
|
|
2407
|
+
}
|
|
2408
|
+
),
|
|
2409
|
+
/* @__PURE__ */ jsx20(Dialog, { open: showConnectInfo, onOpenChange: setShowConnectInfo, children: /* @__PURE__ */ jsxs9(DialogContent, { children: [
|
|
2410
|
+
/* @__PURE__ */ jsxs9(DialogHeader, { children: [
|
|
2411
|
+
/* @__PURE__ */ jsx20(DialogTitle, { children: "Connected Stores" }),
|
|
2412
|
+
/* @__PURE__ */ jsx20(DialogDescription, { children: "How store linking works" })
|
|
2413
|
+
] }),
|
|
2414
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-3 text-sm", children: [
|
|
2415
|
+
/* @__PURE__ */ jsx20("p", { children: "This setting allows optimised curation and processing for SMP and image attributes which remain the same across all SKUs under a style code." }),
|
|
2416
|
+
/* @__PURE__ */ jsxs9("div", { children: [
|
|
2417
|
+
/* @__PURE__ */ jsx20("p", { className: "font-medium", children: "What data is linked:" }),
|
|
2418
|
+
/* @__PURE__ */ jsxs9("ul", { className: "ml-4 mt-1 list-disc text-muted-foreground", children: [
|
|
2419
|
+
/* @__PURE__ */ jsx20("li", { children: "Parent-level attributes (category, type, sleeve, etc.)" }),
|
|
2420
|
+
/* @__PURE__ */ jsx20("li", { children: "Image attributes and processing (SMP URLs, pose type)" }),
|
|
2421
|
+
/* @__PURE__ */ jsx20("li", { children: "Related automations (background removal, classifiers)" })
|
|
2422
|
+
] })
|
|
2423
|
+
] }),
|
|
2424
|
+
/* @__PURE__ */ jsxs9("div", { children: [
|
|
2425
|
+
/* @__PURE__ */ jsx20("p", { className: "font-medium", children: "What data is NOT linked:" }),
|
|
2426
|
+
/* @__PURE__ */ jsxs9("ul", { className: "ml-4 mt-1 list-disc text-muted-foreground", children: [
|
|
2427
|
+
/* @__PURE__ */ jsx20("li", { children: "Child-level data (MRP, size, etc.)" }),
|
|
2428
|
+
/* @__PURE__ */ jsx20("li", { children: "Marketplace-translated attributes & images" }),
|
|
2429
|
+
/* @__PURE__ */ jsx20("li", { children: "Product status tags, export information" })
|
|
2430
|
+
] })
|
|
2431
|
+
] })
|
|
2432
|
+
] })
|
|
2433
|
+
] }) })
|
|
2434
|
+
] });
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2437
|
+
// src/Automations/ProductAutomation.tsx
|
|
2438
|
+
import { useState as useState8, useEffect as useEffect5, useCallback as useCallback6, useRef as useRef5 } from "react";
|
|
2439
|
+
import { ArrowLeft as ArrowLeft2, Search as Search3, Loader2 as Loader26, ChevronDown as ChevronDown2, Play } from "lucide-react";
|
|
2440
|
+
|
|
2441
|
+
// src/Automations/api.ts
|
|
2442
|
+
async function fetchAutomationList(storeId, tag, includeGroupings = false) {
|
|
2443
|
+
const { catalogixBaseUrl } = getApiConfig();
|
|
2444
|
+
const fields = includeGroupings ? "preview,groupings" : "preview";
|
|
2445
|
+
const res = await fetchJson(
|
|
2446
|
+
`${catalogixBaseUrl}/api/v1/fp_orchestrator/${storeId}/automations/list?tag=${tag}&include_fields=${fields}`
|
|
2447
|
+
);
|
|
2448
|
+
return res.data ?? {};
|
|
2449
|
+
}
|
|
2450
|
+
async function fetchAutomationSchema(storeId, automationName, tag) {
|
|
2451
|
+
const { catalogixBaseUrl } = getApiConfig();
|
|
2452
|
+
const res = await fetchJson(
|
|
2453
|
+
`${catalogixBaseUrl}/api/v1/fp_orchestrator/${storeId}/automations/schema?service_name=${automationName}&tag=${tag}`
|
|
2454
|
+
);
|
|
2455
|
+
return res.data?.[automationName] ?? [];
|
|
2456
|
+
}
|
|
2457
|
+
async function fetchDynamicLov(proxyUrl) {
|
|
2458
|
+
const { catalogixBaseUrl } = getApiConfig();
|
|
2459
|
+
return fetchJson(`${catalogixBaseUrl}/api?url=${proxyUrl}`);
|
|
2460
|
+
}
|
|
2461
|
+
async function applyAutomations(storeId, filters, selectedProducts, excludedProducts, selectedPageRange, automationConfigs, userId) {
|
|
2462
|
+
const { catalogixBaseUrl } = getApiConfig();
|
|
2463
|
+
return fetchJson(
|
|
2464
|
+
`${catalogixBaseUrl}/api/v1/store/${storeId}/products/automations`,
|
|
2465
|
+
{
|
|
2466
|
+
method: "POST",
|
|
2467
|
+
headers: { "Content-Type": "application/json" },
|
|
2468
|
+
body: JSON.stringify({
|
|
2469
|
+
filters,
|
|
2470
|
+
selected_products: selectedProducts,
|
|
2471
|
+
excluded_products: excludedProducts,
|
|
2472
|
+
selected_page_range: selectedPageRange,
|
|
2473
|
+
automations: automationConfigs,
|
|
2474
|
+
user_id: userId
|
|
2475
|
+
})
|
|
2476
|
+
}
|
|
2477
|
+
);
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
// src/Automations/utils.ts
|
|
2481
|
+
function replaceFieldsInUrl(url, data) {
|
|
2482
|
+
return url.replace(/\$\((\w+)\)/g, (match, fieldName) => {
|
|
2483
|
+
const value = data[fieldName];
|
|
2484
|
+
return value !== void 0 ? String(value) : match;
|
|
2485
|
+
});
|
|
2486
|
+
}
|
|
2487
|
+
function getAllDependencies(depends, configs, channelsWithVersion) {
|
|
2488
|
+
const dependencies = {};
|
|
2489
|
+
let allSatisfied = true;
|
|
2490
|
+
if (!depends) return { dependencies, allSatisfied };
|
|
2491
|
+
for (const key of Object.keys(depends)) {
|
|
2492
|
+
if (depends[key]) {
|
|
2493
|
+
if (configs[depends[key]] !== void 0) {
|
|
2494
|
+
dependencies[depends[key]] = configs[depends[key]];
|
|
2495
|
+
} else {
|
|
2496
|
+
allSatisfied = false;
|
|
2497
|
+
break;
|
|
2498
|
+
}
|
|
2499
|
+
} else {
|
|
2500
|
+
if ((key === "source_marketplace_ov" || key === "target_marketplace_ov") && channelsWithVersion) {
|
|
2501
|
+
const sourceMp = key.replace("_ov", "");
|
|
2502
|
+
const mpValue = configs[sourceMp];
|
|
2503
|
+
if (mpValue && channelsWithVersion[mpValue]) {
|
|
2504
|
+
dependencies[key] = channelsWithVersion[mpValue];
|
|
2505
|
+
} else {
|
|
2506
|
+
allSatisfied = false;
|
|
2507
|
+
break;
|
|
2508
|
+
}
|
|
2509
|
+
} else {
|
|
2510
|
+
allSatisfied = false;
|
|
2511
|
+
break;
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
return { dependencies, allSatisfied };
|
|
2516
|
+
}
|
|
2517
|
+
function shouldRenderAttribute(conditions, configs) {
|
|
2518
|
+
if (!conditions) return true;
|
|
2519
|
+
for (const { condition, attribute, values } of conditions) {
|
|
2520
|
+
const attrValue = configs[attribute];
|
|
2521
|
+
if (condition === "not_in") {
|
|
2522
|
+
if (attrValue != null && (typeof attrValue === "string" ? attrValue.length : true)) {
|
|
2523
|
+
if (Array.isArray(attrValue)) {
|
|
2524
|
+
if (attrValue.some((v) => values.includes(v))) return false;
|
|
2525
|
+
} else {
|
|
2526
|
+
if (values.includes(attrValue)) return false;
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
} else if (condition === "in") {
|
|
2530
|
+
if (attrValue === void 0) return false;
|
|
2531
|
+
const hasLength = typeof attrValue === "boolean" ? true : typeof attrValue === "string" ? attrValue.length > 0 : Array.isArray(attrValue) ? attrValue.length > 0 : true;
|
|
2532
|
+
if (!hasLength) return false;
|
|
2533
|
+
if (Array.isArray(attrValue)) {
|
|
2534
|
+
if (!attrValue.some((v) => values.includes(v))) return false;
|
|
2535
|
+
} else if (typeof attrValue !== "boolean") {
|
|
2536
|
+
if (!values.includes(attrValue)) return false;
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
return true;
|
|
2541
|
+
}
|
|
2542
|
+
function parseLovResponse(response, curl, configs) {
|
|
2543
|
+
try {
|
|
2544
|
+
const pathStr = curl.substring(curl.indexOf("'") + 1, curl.lastIndexOf("'"));
|
|
2545
|
+
const [pathPart, operationPart] = pathStr.split("|");
|
|
2546
|
+
const paths = pathPart.split(".").map((p) => p.trim()).filter(Boolean).map(
|
|
2547
|
+
(p) => p.startsWith("$(") ? String(configs[p.substring(p.indexOf("$(") + 2, p.lastIndexOf(")"))] ?? p) : p
|
|
2548
|
+
);
|
|
2549
|
+
let temp = response;
|
|
2550
|
+
for (const path of paths) {
|
|
2551
|
+
if (temp == null || typeof temp !== "object") return null;
|
|
2552
|
+
temp = temp[path];
|
|
2553
|
+
}
|
|
2554
|
+
if (temp == null) return null;
|
|
2555
|
+
if (operationPart?.trim() === "key" && typeof temp === "object") {
|
|
2556
|
+
return Object.keys(temp);
|
|
2557
|
+
}
|
|
2558
|
+
return Array.isArray(temp) ? temp : null;
|
|
2559
|
+
} catch {
|
|
2560
|
+
return null;
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
// src/Automations/SchemaFieldRenderer.tsx
|
|
2565
|
+
import { useCallback as useCallback5, useState as useState7 } from "react";
|
|
2566
|
+
import { Check as Check2, ChevronsUpDown as ChevronsUpDown2, Loader2 as Loader25, X } from "lucide-react";
|
|
2567
|
+
import { jsx as jsx21, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2568
|
+
function SchemaFieldRenderer({
|
|
2569
|
+
schema,
|
|
2570
|
+
configs,
|
|
2571
|
+
options,
|
|
2572
|
+
loadingOptions,
|
|
2573
|
+
onConfigChange
|
|
2574
|
+
}) {
|
|
2575
|
+
return /* @__PURE__ */ jsx21("div", { className: "space-y-4", children: schema.map((item, idx) => {
|
|
2576
|
+
if (!shouldRenderAttribute(item.render_if, configs)) return null;
|
|
2577
|
+
if (item.fieldtype === "object_array" && item.mapping_parameters) {
|
|
2578
|
+
return /* @__PURE__ */ jsx21("div", { className: "space-y-3", children: item.mapping_parameters.map((sub, subIdx) => {
|
|
2579
|
+
const value = configs[item.payload_key]?.[0] != null ? configs[item.payload_key][0]?.[sub.payload_key] : void 0;
|
|
2580
|
+
return /* @__PURE__ */ jsx21(
|
|
2581
|
+
FieldSwitch,
|
|
2582
|
+
{
|
|
2583
|
+
item: sub,
|
|
2584
|
+
value,
|
|
2585
|
+
configs,
|
|
2586
|
+
options,
|
|
2587
|
+
loadingOptions,
|
|
2588
|
+
onConfigChange
|
|
2589
|
+
},
|
|
2590
|
+
`${idx}-${subIdx}`
|
|
2591
|
+
);
|
|
2592
|
+
}) }, idx);
|
|
2593
|
+
}
|
|
2594
|
+
return /* @__PURE__ */ jsx21(
|
|
2595
|
+
FieldSwitch,
|
|
2596
|
+
{
|
|
2597
|
+
item,
|
|
2598
|
+
value: configs[item.payload_key],
|
|
2599
|
+
configs,
|
|
2600
|
+
options,
|
|
2601
|
+
loadingOptions,
|
|
2602
|
+
onConfigChange
|
|
2603
|
+
},
|
|
2604
|
+
idx
|
|
2605
|
+
);
|
|
2606
|
+
}) });
|
|
2607
|
+
}
|
|
2608
|
+
function FieldSwitch({
|
|
2609
|
+
item,
|
|
2610
|
+
value,
|
|
2611
|
+
configs,
|
|
2612
|
+
options,
|
|
2613
|
+
loadingOptions,
|
|
2614
|
+
onConfigChange
|
|
2615
|
+
}) {
|
|
2616
|
+
if (!shouldRenderAttribute(item.render_if, configs)) return null;
|
|
2617
|
+
switch (item.fieldtype) {
|
|
2618
|
+
case "freetext":
|
|
2619
|
+
return /* @__PURE__ */ jsx21(
|
|
2620
|
+
FreetextField,
|
|
2621
|
+
{
|
|
2622
|
+
item,
|
|
2623
|
+
value,
|
|
2624
|
+
onChange: (v) => onConfigChange(item.payload_key, v, item)
|
|
2625
|
+
}
|
|
2626
|
+
);
|
|
2627
|
+
case "freetext_list":
|
|
2628
|
+
return /* @__PURE__ */ jsx21(
|
|
2629
|
+
FreetextListField,
|
|
2630
|
+
{
|
|
2631
|
+
item,
|
|
2632
|
+
value,
|
|
2633
|
+
onChange: (v) => onConfigChange(item.payload_key, v, item)
|
|
2634
|
+
}
|
|
2635
|
+
);
|
|
2636
|
+
case "lov_singleselect":
|
|
2637
|
+
return /* @__PURE__ */ jsx21(
|
|
2638
|
+
SingleSelectField,
|
|
2639
|
+
{
|
|
2640
|
+
item,
|
|
2641
|
+
value,
|
|
2642
|
+
options: options[item.payload_key] ?? item.lov ?? [],
|
|
2643
|
+
loading: !!loadingOptions[item.payload_key],
|
|
2644
|
+
onChange: (v) => onConfigChange(item.payload_key, v, item)
|
|
2645
|
+
}
|
|
2646
|
+
);
|
|
2647
|
+
case "lov_multipleselect":
|
|
2648
|
+
return /* @__PURE__ */ jsx21(
|
|
2649
|
+
MultiSelectField,
|
|
2650
|
+
{
|
|
2651
|
+
item,
|
|
2652
|
+
value,
|
|
2653
|
+
options: options[item.payload_key] ?? item.lov ?? [],
|
|
2654
|
+
loading: !!loadingOptions[item.payload_key],
|
|
2655
|
+
onChange: (v) => onConfigChange(item.payload_key, v, item)
|
|
2656
|
+
}
|
|
2657
|
+
);
|
|
2658
|
+
case "image_singleselect":
|
|
2659
|
+
return /* @__PURE__ */ jsx21(
|
|
2660
|
+
ImageSelectField,
|
|
2661
|
+
{
|
|
2662
|
+
item,
|
|
2663
|
+
value,
|
|
2664
|
+
onChange: (v) => onConfigChange(item.payload_key, v, item)
|
|
2665
|
+
}
|
|
2666
|
+
);
|
|
2667
|
+
case "boolean":
|
|
2668
|
+
return /* @__PURE__ */ jsx21(
|
|
2669
|
+
BooleanField,
|
|
2670
|
+
{
|
|
2671
|
+
item,
|
|
2672
|
+
value,
|
|
2673
|
+
onChange: (v) => onConfigChange(item.payload_key, v, item)
|
|
2674
|
+
}
|
|
2675
|
+
);
|
|
2676
|
+
default:
|
|
2677
|
+
return null;
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
function FreetextField({
|
|
2681
|
+
item,
|
|
2682
|
+
value,
|
|
2683
|
+
onChange
|
|
2684
|
+
}) {
|
|
2685
|
+
return /* @__PURE__ */ jsxs10("div", { className: "space-y-1.5", children: [
|
|
2686
|
+
/* @__PURE__ */ jsxs10(Label, { children: [
|
|
2687
|
+
item.title,
|
|
2688
|
+
item.required && /* @__PURE__ */ jsx21("span", { className: "text-destructive", children: " *" })
|
|
2689
|
+
] }),
|
|
2690
|
+
item.description && /* @__PURE__ */ jsx21("p", { className: "text-xs text-muted-foreground", children: item.description }),
|
|
2691
|
+
/* @__PURE__ */ jsx21(
|
|
2692
|
+
Input,
|
|
2693
|
+
{
|
|
2694
|
+
value: value ?? "",
|
|
2695
|
+
onChange: (e) => onChange(e.target.value),
|
|
2696
|
+
type: item.valuetype === "integer" ? "number" : "text",
|
|
2697
|
+
min: item.minimum
|
|
2698
|
+
}
|
|
2699
|
+
)
|
|
2700
|
+
] });
|
|
2701
|
+
}
|
|
2702
|
+
function FreetextListField({
|
|
2703
|
+
item,
|
|
2704
|
+
value,
|
|
2705
|
+
onChange
|
|
2706
|
+
}) {
|
|
2707
|
+
const [draft, setDraft] = useState7("");
|
|
2708
|
+
const keywords = value ?? [];
|
|
2709
|
+
const addKeyword = () => {
|
|
2710
|
+
const trimmed = draft.trim();
|
|
2711
|
+
if (trimmed && !keywords.includes(trimmed)) {
|
|
2712
|
+
onChange([...keywords, trimmed]);
|
|
2713
|
+
setDraft("");
|
|
2714
|
+
}
|
|
2715
|
+
};
|
|
2716
|
+
return /* @__PURE__ */ jsxs10("div", { className: "space-y-1.5", children: [
|
|
2717
|
+
/* @__PURE__ */ jsxs10(Label, { children: [
|
|
2718
|
+
item.title,
|
|
2719
|
+
item.required && /* @__PURE__ */ jsx21("span", { className: "text-destructive", children: " *" })
|
|
2720
|
+
] }),
|
|
2721
|
+
item.description && /* @__PURE__ */ jsx21("p", { className: "text-xs text-muted-foreground", children: item.description }),
|
|
2722
|
+
/* @__PURE__ */ jsxs10("div", { className: "flex gap-2", children: [
|
|
2723
|
+
/* @__PURE__ */ jsx21(
|
|
2724
|
+
Input,
|
|
2725
|
+
{
|
|
2726
|
+
value: draft,
|
|
2727
|
+
onChange: (e) => setDraft(e.target.value),
|
|
2728
|
+
onKeyDown: (e) => e.key === "Enter" && (e.preventDefault(), addKeyword()),
|
|
2729
|
+
placeholder: "Type and press Enter"
|
|
2730
|
+
}
|
|
2731
|
+
),
|
|
2732
|
+
/* @__PURE__ */ jsx21(Button, { variant: "outline", size: "sm", onClick: addKeyword, type: "button", children: "Add" })
|
|
2733
|
+
] }),
|
|
2734
|
+
keywords.length > 0 && /* @__PURE__ */ jsx21("div", { className: "flex flex-wrap gap-1.5 pt-1", children: keywords.map((kw) => /* @__PURE__ */ jsxs10(Badge, { variant: "secondary", className: "gap-1 pr-1", children: [
|
|
2735
|
+
kw,
|
|
2736
|
+
/* @__PURE__ */ jsx21(
|
|
2737
|
+
"button",
|
|
2738
|
+
{
|
|
2739
|
+
type: "button",
|
|
2740
|
+
onClick: () => onChange(keywords.filter((k) => k !== kw)),
|
|
2741
|
+
className: "ml-0.5 rounded-full hover:bg-muted",
|
|
2742
|
+
children: /* @__PURE__ */ jsx21(X, { className: "size-3" })
|
|
2743
|
+
}
|
|
2744
|
+
)
|
|
2745
|
+
] }, kw)) })
|
|
2746
|
+
] });
|
|
2747
|
+
}
|
|
2748
|
+
function SingleSelectField({
|
|
2749
|
+
item,
|
|
2750
|
+
value,
|
|
2751
|
+
options: lovOptions,
|
|
2752
|
+
loading,
|
|
2753
|
+
onChange
|
|
2754
|
+
}) {
|
|
2755
|
+
const strOptions = Array.isArray(lovOptions) ? lovOptions.map(String) : [];
|
|
2756
|
+
const strValue = value != null ? String(value) : "";
|
|
2757
|
+
return /* @__PURE__ */ jsxs10("div", { className: "space-y-1.5", children: [
|
|
2758
|
+
/* @__PURE__ */ jsxs10(Label, { children: [
|
|
2759
|
+
item.title,
|
|
2760
|
+
item.required && /* @__PURE__ */ jsx21("span", { className: "text-destructive", children: " *" })
|
|
2761
|
+
] }),
|
|
2762
|
+
item.description && /* @__PURE__ */ jsx21("p", { className: "text-xs text-muted-foreground", children: item.description }),
|
|
2763
|
+
loading ? /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
2764
|
+
/* @__PURE__ */ jsx21(Loader25, { className: "size-4 animate-spin" }),
|
|
2765
|
+
"Loading options..."
|
|
2766
|
+
] }) : /* @__PURE__ */ jsxs10(
|
|
2767
|
+
Select,
|
|
2768
|
+
{
|
|
2769
|
+
value: strValue,
|
|
2770
|
+
onValueChange: (v) => onChange(item.valuetype === "integer" ? Number(v) : v),
|
|
2771
|
+
children: [
|
|
2772
|
+
/* @__PURE__ */ jsx21(SelectTrigger, { children: /* @__PURE__ */ jsx21(SelectValue, { placeholder: "Select..." }) }),
|
|
2773
|
+
/* @__PURE__ */ jsx21(SelectContent, { children: strOptions.map((opt) => /* @__PURE__ */ jsx21(SelectItem, { value: opt, children: opt }, opt)) })
|
|
2774
|
+
]
|
|
2775
|
+
}
|
|
2776
|
+
)
|
|
2777
|
+
] });
|
|
2778
|
+
}
|
|
2779
|
+
function MultiSelectField({
|
|
2780
|
+
item,
|
|
2781
|
+
value,
|
|
2782
|
+
options: lovOptions,
|
|
2783
|
+
loading,
|
|
2784
|
+
onChange
|
|
2785
|
+
}) {
|
|
2786
|
+
const [open, setOpen] = useState7(false);
|
|
2787
|
+
const selected = value ?? [];
|
|
2788
|
+
const strOptions = Array.isArray(lovOptions) ? lovOptions.map(String) : [];
|
|
2789
|
+
const toggle = useCallback5(
|
|
2790
|
+
(opt) => {
|
|
2791
|
+
onChange(
|
|
2792
|
+
selected.includes(opt) ? selected.filter((s) => s !== opt) : [...selected, opt]
|
|
2793
|
+
);
|
|
2794
|
+
},
|
|
2795
|
+
[selected, onChange]
|
|
2796
|
+
);
|
|
2797
|
+
return /* @__PURE__ */ jsxs10("div", { className: "space-y-1.5", children: [
|
|
2798
|
+
/* @__PURE__ */ jsxs10(Label, { children: [
|
|
2799
|
+
item.title,
|
|
2800
|
+
item.required && /* @__PURE__ */ jsx21("span", { className: "text-destructive", children: " *" })
|
|
2801
|
+
] }),
|
|
2802
|
+
item.description && /* @__PURE__ */ jsx21("p", { className: "text-xs text-muted-foreground", children: item.description }),
|
|
2803
|
+
loading ? /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
|
|
2804
|
+
/* @__PURE__ */ jsx21(Loader25, { className: "size-4 animate-spin" }),
|
|
2805
|
+
"Loading options..."
|
|
2806
|
+
] }) : /* @__PURE__ */ jsxs10(Popover, { open, onOpenChange: setOpen, children: [
|
|
2807
|
+
/* @__PURE__ */ jsx21(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs10(
|
|
2808
|
+
Button,
|
|
2809
|
+
{
|
|
2810
|
+
variant: "outline",
|
|
2811
|
+
className: "w-full justify-between font-normal",
|
|
2812
|
+
children: [
|
|
2813
|
+
selected.length > 0 ? /* @__PURE__ */ jsx21("div", { className: "flex flex-wrap gap-1", children: selected.map((s) => /* @__PURE__ */ jsx21(Badge, { variant: "secondary", className: "text-xs", children: s }, s)) }) : /* @__PURE__ */ jsx21("span", { className: "text-muted-foreground", children: "Select..." }),
|
|
2814
|
+
/* @__PURE__ */ jsx21(ChevronsUpDown2, { className: "ml-2 size-4 shrink-0 opacity-50" })
|
|
2815
|
+
]
|
|
2816
|
+
}
|
|
2817
|
+
) }),
|
|
2818
|
+
/* @__PURE__ */ jsx21(PopoverContent, { className: "w-[260px] p-0", align: "start", children: /* @__PURE__ */ jsxs10(Command, { children: [
|
|
2819
|
+
/* @__PURE__ */ jsx21(CommandInput, { placeholder: "Search..." }),
|
|
2820
|
+
/* @__PURE__ */ jsxs10(CommandList, { children: [
|
|
2821
|
+
/* @__PURE__ */ jsx21(CommandEmpty, { children: "No match." }),
|
|
2822
|
+
/* @__PURE__ */ jsx21(CommandGroup, { children: strOptions.map((opt) => {
|
|
2823
|
+
const isSelected = selected.includes(opt);
|
|
2824
|
+
return /* @__PURE__ */ jsxs10(
|
|
2825
|
+
CommandItem,
|
|
2826
|
+
{
|
|
2827
|
+
value: opt,
|
|
2828
|
+
onSelect: () => toggle(opt),
|
|
2829
|
+
children: [
|
|
2830
|
+
/* @__PURE__ */ jsx21(
|
|
2831
|
+
Check2,
|
|
2832
|
+
{
|
|
2833
|
+
className: cn(
|
|
2834
|
+
"mr-2 size-4",
|
|
2835
|
+
isSelected ? "opacity-100" : "opacity-0"
|
|
2836
|
+
)
|
|
2837
|
+
}
|
|
2838
|
+
),
|
|
2839
|
+
opt
|
|
2840
|
+
]
|
|
2841
|
+
},
|
|
2842
|
+
opt
|
|
2843
|
+
);
|
|
2844
|
+
}) })
|
|
2845
|
+
] })
|
|
2846
|
+
] }) })
|
|
2847
|
+
] })
|
|
2848
|
+
] });
|
|
2849
|
+
}
|
|
2850
|
+
function ImageSelectField({
|
|
2851
|
+
item,
|
|
2852
|
+
value,
|
|
2853
|
+
onChange
|
|
2854
|
+
}) {
|
|
2855
|
+
const images = item.lov ?? [];
|
|
2856
|
+
return /* @__PURE__ */ jsxs10("div", { className: "space-y-1.5", children: [
|
|
2857
|
+
/* @__PURE__ */ jsxs10(Label, { children: [
|
|
2858
|
+
item.title,
|
|
2859
|
+
item.required && /* @__PURE__ */ jsx21("span", { className: "text-destructive", children: " *" })
|
|
2860
|
+
] }),
|
|
2861
|
+
/* @__PURE__ */ jsx21("div", { className: "flex flex-wrap gap-3", children: images.map((url) => /* @__PURE__ */ jsxs10(
|
|
2862
|
+
"button",
|
|
2863
|
+
{
|
|
2864
|
+
type: "button",
|
|
2865
|
+
onClick: () => onChange(url),
|
|
2866
|
+
className: cn(
|
|
2867
|
+
"relative overflow-hidden rounded-lg border-2 transition-colors",
|
|
2868
|
+
value === url ? "border-primary ring-2 ring-primary/30" : "border-border hover:border-primary/50"
|
|
2869
|
+
),
|
|
2870
|
+
children: [
|
|
2871
|
+
/* @__PURE__ */ jsx21("img", { src: url, alt: "", className: "size-20 object-cover" }),
|
|
2872
|
+
value === url && /* @__PURE__ */ jsx21("div", { className: "absolute right-1 top-1 rounded-full bg-primary p-0.5 text-primary-foreground", children: /* @__PURE__ */ jsx21(Check2, { className: "size-3" }) })
|
|
2873
|
+
]
|
|
2874
|
+
},
|
|
2875
|
+
url
|
|
2876
|
+
)) })
|
|
2877
|
+
] });
|
|
2878
|
+
}
|
|
2879
|
+
function BooleanField({
|
|
2880
|
+
item,
|
|
2881
|
+
value,
|
|
2882
|
+
onChange
|
|
2883
|
+
}) {
|
|
2884
|
+
return /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 py-1", children: [
|
|
2885
|
+
/* @__PURE__ */ jsx21(
|
|
2886
|
+
Checkbox,
|
|
2887
|
+
{
|
|
2888
|
+
checked: !!value,
|
|
2889
|
+
onCheckedChange: (checked) => onChange(!!checked)
|
|
2890
|
+
}
|
|
2891
|
+
),
|
|
2892
|
+
/* @__PURE__ */ jsxs10(Label, { className: "cursor-pointer", onClick: () => onChange(!value), children: [
|
|
2893
|
+
item.title,
|
|
2894
|
+
item.required && /* @__PURE__ */ jsx21("span", { className: "text-destructive", children: " *" })
|
|
2895
|
+
] })
|
|
2896
|
+
] });
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
// src/Automations/ProductAutomation.tsx
|
|
2900
|
+
import { Fragment as Fragment4, jsx as jsx22, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2901
|
+
function ProductAutomation({
|
|
2902
|
+
storeId,
|
|
2903
|
+
workspaceId,
|
|
2904
|
+
channelsWithVersion,
|
|
2905
|
+
selectedProducts,
|
|
2906
|
+
excludedProducts,
|
|
2907
|
+
totalProducts,
|
|
2908
|
+
productsCount: productsCountProp,
|
|
2909
|
+
filters,
|
|
2910
|
+
selectedPageRange,
|
|
2911
|
+
userId,
|
|
2912
|
+
onClose,
|
|
2913
|
+
onSubmit,
|
|
2914
|
+
onAutomationApplied
|
|
2915
|
+
}) {
|
|
2916
|
+
const [loading, setLoading] = useState8(true);
|
|
2917
|
+
const [automationGroups, setAutomationGroups] = useState8({});
|
|
2918
|
+
const [expandedGroups, setExpandedGroups] = useState8([]);
|
|
2919
|
+
const [searchQuery, setSearchQuery] = useState8("");
|
|
2920
|
+
const [selected, setSelected] = useState8(null);
|
|
2921
|
+
const [schema, setSchema] = useState8([]);
|
|
2922
|
+
const [configs, setConfigs] = useState8({});
|
|
2923
|
+
const [options, setOptions] = useState8({});
|
|
2924
|
+
const [loadingOptions, setLoadingOptions] = useState8({});
|
|
2925
|
+
const [schemaLoading, setSchemaLoading] = useState8(false);
|
|
2926
|
+
const [applying, setApplying] = useState8(false);
|
|
2927
|
+
const configsRef = useRef5(configs);
|
|
2928
|
+
configsRef.current = configs;
|
|
2929
|
+
const optionsRef = useRef5(options);
|
|
2930
|
+
optionsRef.current = options;
|
|
2931
|
+
const loadingOptionsRef = useRef5(loadingOptions);
|
|
2932
|
+
loadingOptionsRef.current = loadingOptions;
|
|
2933
|
+
const productsCount = selectedProducts.includes("all") ? excludedProducts.length === 0 ? totalProducts : totalProducts - excludedProducts.length : productsCountProp;
|
|
2934
|
+
useEffect5(() => {
|
|
2935
|
+
setLoading(true);
|
|
2936
|
+
fetchAutomationList(storeId, "product", true).then((data) => {
|
|
2937
|
+
const groups = {};
|
|
2938
|
+
for (const [name, item] of Object.entries(data)) {
|
|
2939
|
+
const itemGroups = item.groupings?.length ? item.groupings : ["Others"];
|
|
2940
|
+
for (const g of itemGroups) {
|
|
2941
|
+
if (!groups[g]) groups[g] = [];
|
|
2942
|
+
groups[g].push({ ...item, automation: name });
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
setAutomationGroups(groups);
|
|
2946
|
+
const sorted = Object.keys(groups).sort();
|
|
2947
|
+
if (sorted.length > 0) setExpandedGroups([sorted[0]]);
|
|
2948
|
+
}).catch(console.error).finally(() => setLoading(false));
|
|
2949
|
+
}, [storeId]);
|
|
2950
|
+
useEffect5(() => {
|
|
2951
|
+
if (!selected) {
|
|
2952
|
+
setSchema([]);
|
|
2953
|
+
setConfigs({});
|
|
2954
|
+
setOptions({});
|
|
2955
|
+
setLoadingOptions({});
|
|
2956
|
+
return;
|
|
2957
|
+
}
|
|
2958
|
+
setSchemaLoading(true);
|
|
2959
|
+
fetchAutomationSchema(storeId, selected.automation, "product").then((items) => {
|
|
2960
|
+
setSchema(items);
|
|
2961
|
+
const defaults = {};
|
|
2962
|
+
for (const item of items) {
|
|
2963
|
+
if (item.default !== void 0) defaults[item.payload_key] = item.default;
|
|
2964
|
+
}
|
|
2965
|
+
setConfigs(defaults);
|
|
2966
|
+
configsRef.current = defaults;
|
|
2967
|
+
}).catch(console.error).finally(() => setSchemaLoading(false));
|
|
2968
|
+
}, [selected, storeId]);
|
|
2969
|
+
useEffect5(() => {
|
|
2970
|
+
if (schema.length === 0) return;
|
|
2971
|
+
const flatItems = schema.flatMap((s) => s.mapping_parameters ?? [s]);
|
|
2972
|
+
for (const item of flatItems) {
|
|
2973
|
+
if (!item.curl) continue;
|
|
2974
|
+
if (optionsRef.current[item.payload_key]) continue;
|
|
2975
|
+
const { dependencies, allSatisfied } = getAllDependencies(
|
|
2976
|
+
item.depends,
|
|
2977
|
+
configsRef.current,
|
|
2978
|
+
channelsWithVersion
|
|
2979
|
+
);
|
|
2980
|
+
if (allSatisfied) {
|
|
2981
|
+
const url = replaceFieldsInUrl(
|
|
2982
|
+
item.curl.split(" ")[1],
|
|
2983
|
+
dependencies
|
|
2984
|
+
);
|
|
2985
|
+
setLoadingOptions((prev) => ({ ...prev, [item.payload_key]: true }));
|
|
2986
|
+
fetchDynamicLov(url).then((res) => {
|
|
2987
|
+
const data = parseLovResponse(res, item.curl, configsRef.current);
|
|
2988
|
+
if (data) {
|
|
2989
|
+
setOptions((prev) => ({ ...prev, [item.payload_key]: data }));
|
|
2990
|
+
}
|
|
2991
|
+
}).catch(console.error).finally(
|
|
2992
|
+
() => setLoadingOptions((prev) => ({ ...prev, [item.payload_key]: false }))
|
|
2993
|
+
);
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
}, [schema, configs, channelsWithVersion]);
|
|
2997
|
+
const handleConfigChange = useCallback6(
|
|
2998
|
+
(key, value, item) => {
|
|
2999
|
+
setConfigs((prev) => {
|
|
3000
|
+
const parentItem = schema.find(
|
|
3001
|
+
(s) => s.mapping_parameters?.some((m) => m.payload_key === key)
|
|
3002
|
+
);
|
|
3003
|
+
if (parentItem) {
|
|
3004
|
+
const existing = prev[parentItem.payload_key] ?? [{}];
|
|
3005
|
+
const updated = [{ ...existing[0], [key]: value }];
|
|
3006
|
+
return { ...prev, [parentItem.payload_key]: updated };
|
|
3007
|
+
}
|
|
3008
|
+
return { ...prev, [key]: value };
|
|
3009
|
+
});
|
|
3010
|
+
if (item.linked?.length) {
|
|
3011
|
+
for (const linkedKey of item.linked) {
|
|
3012
|
+
setOptions((prev) => ({ ...prev, [linkedKey]: [] }));
|
|
3013
|
+
setConfigs((prev) => {
|
|
3014
|
+
const next = { ...prev };
|
|
3015
|
+
delete next[linkedKey];
|
|
3016
|
+
return next;
|
|
3017
|
+
});
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
},
|
|
3021
|
+
[schema]
|
|
3022
|
+
);
|
|
3023
|
+
const isRunAllowed = useCallback6(() => {
|
|
3024
|
+
for (const item of schema) {
|
|
3025
|
+
if (!shouldRenderAttribute(item.render_if, configs)) continue;
|
|
3026
|
+
if (item.required) {
|
|
3027
|
+
const val = configs[item.payload_key];
|
|
3028
|
+
if (val === void 0) return false;
|
|
3029
|
+
if (Array.isArray(val) && val.length === 0) return false;
|
|
3030
|
+
}
|
|
3031
|
+
if (item.minimum != null && Number(configs[item.payload_key]) < item.minimum) {
|
|
3032
|
+
return false;
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
return true;
|
|
3036
|
+
}, [schema, configs]);
|
|
3037
|
+
const handleRun = async () => {
|
|
3038
|
+
if (!selected || !isRunAllowed()) return;
|
|
3039
|
+
if (onAutomationApplied) {
|
|
3040
|
+
onAutomationApplied({ [selected.automation]: configs });
|
|
3041
|
+
onClose();
|
|
3042
|
+
return;
|
|
3043
|
+
}
|
|
3044
|
+
setApplying(true);
|
|
3045
|
+
try {
|
|
3046
|
+
const res = await applyAutomations(
|
|
3047
|
+
storeId,
|
|
3048
|
+
filters,
|
|
3049
|
+
selectedProducts,
|
|
3050
|
+
excludedProducts,
|
|
3051
|
+
selectedPageRange,
|
|
3052
|
+
{ [selected.automation]: configs },
|
|
3053
|
+
userId
|
|
3054
|
+
);
|
|
3055
|
+
if (res.status?.code === 0) {
|
|
3056
|
+
onSubmit?.({ [selected.automation]: configs });
|
|
3057
|
+
onClose();
|
|
3058
|
+
}
|
|
3059
|
+
} catch (err) {
|
|
3060
|
+
console.error("Apply automation failed:", err);
|
|
3061
|
+
} finally {
|
|
3062
|
+
setApplying(false);
|
|
3063
|
+
}
|
|
3064
|
+
};
|
|
3065
|
+
if (!selected) {
|
|
3066
|
+
const allItems = Object.values(automationGroups).flat();
|
|
3067
|
+
const unique = allItems.filter(
|
|
3068
|
+
(item, idx, arr) => arr.findIndex((a) => a.automation === item.automation) === idx
|
|
3069
|
+
);
|
|
3070
|
+
const filtered = searchQuery ? unique.filter(
|
|
3071
|
+
(a) => a.display_name?.toLowerCase().includes(searchQuery.toLowerCase()) || a.automation.toLowerCase().includes(searchQuery.toLowerCase())
|
|
3072
|
+
) : null;
|
|
3073
|
+
return /* @__PURE__ */ jsxs11("div", { className: "flex h-full flex-col gap-3 p-3", children: [
|
|
3074
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
|
|
3075
|
+
/* @__PURE__ */ jsx22(Button, { variant: "ghost", size: "icon", onClick: onClose, children: /* @__PURE__ */ jsx22(ArrowLeft2, { className: "size-4" }) }),
|
|
3076
|
+
/* @__PURE__ */ jsx22("h3", { className: "text-base font-semibold", children: "Automations" }),
|
|
3077
|
+
/* @__PURE__ */ jsxs11(Badge, { variant: "secondary", children: [
|
|
3078
|
+
productsCount,
|
|
3079
|
+
" products"
|
|
3080
|
+
] })
|
|
3081
|
+
] }),
|
|
3082
|
+
/* @__PURE__ */ jsxs11("div", { className: "relative", children: [
|
|
3083
|
+
/* @__PURE__ */ jsx22(Search3, { className: "absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" }),
|
|
3084
|
+
/* @__PURE__ */ jsx22(
|
|
3085
|
+
Input,
|
|
3086
|
+
{
|
|
3087
|
+
placeholder: "Search automations...",
|
|
3088
|
+
value: searchQuery,
|
|
3089
|
+
onChange: (e) => setSearchQuery(e.target.value),
|
|
3090
|
+
className: "pl-9"
|
|
3091
|
+
}
|
|
3092
|
+
)
|
|
3093
|
+
] }),
|
|
3094
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-4 border-b px-2 pb-2 text-xs font-medium text-muted-foreground", children: [
|
|
3095
|
+
/* @__PURE__ */ jsx22("span", { className: "flex-[5]", children: "Automation" }),
|
|
3096
|
+
/* @__PURE__ */ jsx22("span", { className: "flex-[1.5] text-center", children: "Est. Time" }),
|
|
3097
|
+
/* @__PURE__ */ jsx22("span", { className: "flex-[1.5] text-center", children: "Credits" })
|
|
3098
|
+
] }),
|
|
3099
|
+
/* @__PURE__ */ jsx22("div", { className: "flex-1 overflow-y-auto", children: loading ? /* @__PURE__ */ jsx22("div", { className: "space-y-3 p-2", children: Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsx22(Skeleton, { className: "h-14 w-full rounded-md" }, i)) }) : filtered ? filtered.map((item) => /* @__PURE__ */ jsx22(
|
|
3100
|
+
AutomationRow,
|
|
3101
|
+
{
|
|
3102
|
+
item,
|
|
3103
|
+
productsCount,
|
|
3104
|
+
onClick: () => setSelected(item)
|
|
3105
|
+
},
|
|
3106
|
+
item.automation
|
|
3107
|
+
)) : Object.keys(automationGroups).sort().map((group) => /* @__PURE__ */ jsxs11("div", { children: [
|
|
3108
|
+
/* @__PURE__ */ jsxs11(
|
|
3109
|
+
"button",
|
|
3110
|
+
{
|
|
3111
|
+
type: "button",
|
|
3112
|
+
onClick: () => setExpandedGroups(
|
|
3113
|
+
(prev) => prev.includes(group) ? prev.filter((g) => g !== group) : [...prev, group]
|
|
3114
|
+
),
|
|
3115
|
+
className: "flex w-full items-center justify-between px-2 py-2 text-sm font-semibold text-muted-foreground hover:text-foreground",
|
|
3116
|
+
children: [
|
|
3117
|
+
group,
|
|
3118
|
+
/* @__PURE__ */ jsx22(
|
|
3119
|
+
ChevronDown2,
|
|
3120
|
+
{
|
|
3121
|
+
className: cn(
|
|
3122
|
+
"size-4 transition-transform",
|
|
3123
|
+
expandedGroups.includes(group) && "rotate-180"
|
|
3124
|
+
)
|
|
3125
|
+
}
|
|
3126
|
+
)
|
|
3127
|
+
]
|
|
3128
|
+
}
|
|
3129
|
+
),
|
|
3130
|
+
expandedGroups.includes(group) && automationGroups[group].map((item) => /* @__PURE__ */ jsx22(
|
|
3131
|
+
AutomationRow,
|
|
3132
|
+
{
|
|
3133
|
+
item,
|
|
3134
|
+
productsCount,
|
|
3135
|
+
onClick: () => setSelected(item)
|
|
3136
|
+
},
|
|
3137
|
+
item.automation
|
|
3138
|
+
))
|
|
3139
|
+
] }, group)) })
|
|
3140
|
+
] });
|
|
3141
|
+
}
|
|
3142
|
+
return /* @__PURE__ */ jsxs11("div", { className: "flex h-full flex-col gap-3 p-3", children: [
|
|
3143
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
|
|
3144
|
+
/* @__PURE__ */ jsx22(Button, { variant: "ghost", size: "icon", onClick: () => setSelected(null), children: /* @__PURE__ */ jsx22(ArrowLeft2, { className: "size-4" }) }),
|
|
3145
|
+
/* @__PURE__ */ jsx22("h3", { className: "text-base font-semibold", children: selected.display_name })
|
|
3146
|
+
] }),
|
|
3147
|
+
selected.preview && /* @__PURE__ */ jsxs11("div", { className: "space-y-2 rounded-lg bg-muted/50 p-3 text-sm", children: [
|
|
3148
|
+
selected.preview.info_text.map((text, i) => /* @__PURE__ */ jsx22("p", { className: "text-muted-foreground", children: text }, i)),
|
|
3149
|
+
selected.preview.media.length > 0 && /* @__PURE__ */ jsx22("div", { className: "flex flex-wrap gap-2 pt-1", children: selected.preview.media.map(
|
|
3150
|
+
(m, i) => m.type === "image" ? /* @__PURE__ */ jsx22(
|
|
3151
|
+
"img",
|
|
3152
|
+
{
|
|
3153
|
+
src: m.src,
|
|
3154
|
+
alt: "",
|
|
3155
|
+
className: "max-h-32 rounded-md object-contain"
|
|
3156
|
+
},
|
|
3157
|
+
i
|
|
3158
|
+
) : null
|
|
3159
|
+
) })
|
|
3160
|
+
] }),
|
|
3161
|
+
/* @__PURE__ */ jsx22(Separator2, {}),
|
|
3162
|
+
/* @__PURE__ */ jsx22("div", { className: "flex-1 overflow-y-auto", children: schemaLoading ? /* @__PURE__ */ jsx22("div", { className: "space-y-3 p-2", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsx22(Skeleton, { className: "h-12 w-full rounded-md" }, i)) }) : /* @__PURE__ */ jsx22(
|
|
3163
|
+
SchemaFieldRenderer,
|
|
3164
|
+
{
|
|
3165
|
+
schema,
|
|
3166
|
+
configs,
|
|
3167
|
+
options,
|
|
3168
|
+
loadingOptions,
|
|
3169
|
+
onConfigChange: handleConfigChange
|
|
3170
|
+
}
|
|
3171
|
+
) }),
|
|
3172
|
+
/* @__PURE__ */ jsx22(Separator2, {}),
|
|
3173
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-2", children: [
|
|
3174
|
+
/* @__PURE__ */ jsx22(
|
|
3175
|
+
Button,
|
|
3176
|
+
{
|
|
3177
|
+
onClick: handleRun,
|
|
3178
|
+
disabled: !isRunAllowed() || applying,
|
|
3179
|
+
className: "flex-1",
|
|
3180
|
+
children: applying ? /* @__PURE__ */ jsxs11(Fragment4, { children: [
|
|
3181
|
+
/* @__PURE__ */ jsx22(Loader26, { className: "size-4 animate-spin" }),
|
|
3182
|
+
"Applying..."
|
|
3183
|
+
] }) : /* @__PURE__ */ jsxs11(Fragment4, { children: [
|
|
3184
|
+
/* @__PURE__ */ jsx22(Play, { className: "size-4" }),
|
|
3185
|
+
"Run Automation"
|
|
3186
|
+
] })
|
|
3187
|
+
}
|
|
3188
|
+
),
|
|
3189
|
+
/* @__PURE__ */ jsx22(Button, { variant: "outline", onClick: () => setSelected(null), children: "Cancel" })
|
|
3190
|
+
] }),
|
|
3191
|
+
/* @__PURE__ */ jsx22("p", { className: "text-center text-xs text-muted-foreground", children: "By applying, you will override existing automations for the selected products with this new run." })
|
|
3192
|
+
] });
|
|
3193
|
+
}
|
|
3194
|
+
function AutomationRow({
|
|
3195
|
+
item,
|
|
3196
|
+
productsCount,
|
|
3197
|
+
onClick
|
|
3198
|
+
}) {
|
|
3199
|
+
const estTime = item.estimate_time_saved ? formatTimeSaved(item.estimate_time_saved * productsCount) : "\u2014";
|
|
3200
|
+
const credits = item.credits ? Math.ceil(item.credits * productsCount) : 0;
|
|
3201
|
+
return /* @__PURE__ */ jsxs11(
|
|
3202
|
+
"button",
|
|
3203
|
+
{
|
|
3204
|
+
type: "button",
|
|
3205
|
+
onClick,
|
|
3206
|
+
className: "flex w-full items-center gap-4 rounded-md px-2 py-2.5 text-left hover:bg-accent",
|
|
3207
|
+
children: [
|
|
3208
|
+
/* @__PURE__ */ jsxs11("div", { className: "flex-[5] min-w-0", children: [
|
|
3209
|
+
/* @__PURE__ */ jsx22("div", { className: "text-sm font-medium", children: item.display_name }),
|
|
3210
|
+
item.description && /* @__PURE__ */ jsx22("div", { className: "truncate text-xs text-muted-foreground", children: item.description })
|
|
3211
|
+
] }),
|
|
3212
|
+
/* @__PURE__ */ jsx22("div", { className: "flex-[1.5] text-center text-xs text-muted-foreground", children: estTime }),
|
|
3213
|
+
/* @__PURE__ */ jsx22("div", { className: "flex-[1.5] text-center text-xs text-muted-foreground", children: credits })
|
|
3214
|
+
]
|
|
3215
|
+
}
|
|
3216
|
+
);
|
|
3217
|
+
}
|
|
3218
|
+
function formatTimeSaved(totalSeconds) {
|
|
3219
|
+
if (totalSeconds >= 3600) return `${Math.ceil(totalSeconds / 3600)} hrs`;
|
|
3220
|
+
if (totalSeconds >= 60) return `${Math.ceil(totalSeconds / 60)} mins`;
|
|
3221
|
+
return `${totalSeconds} secs`;
|
|
3222
|
+
}
|
|
3223
|
+
|
|
3224
|
+
// src/Automations/StoreAutomation.tsx
|
|
3225
|
+
import { useState as useState9, useEffect as useEffect6, useCallback as useCallback7, useRef as useRef6 } from "react";
|
|
3226
|
+
import { ArrowLeft as ArrowLeft3, Settings } from "lucide-react";
|
|
3227
|
+
import { Fragment as Fragment5, jsx as jsx23, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
3228
|
+
function StoreAutomation({
|
|
3229
|
+
storeId,
|
|
3230
|
+
channelsWithVersion,
|
|
3231
|
+
enabledAutomations = {},
|
|
3232
|
+
onSubmit,
|
|
3233
|
+
onClose
|
|
3234
|
+
}) {
|
|
3235
|
+
const [loading, setLoading] = useState9(true);
|
|
3236
|
+
const [automations, setAutomations] = useState9([]);
|
|
3237
|
+
const [selectedConfigs, setSelectedConfigs] = useState9({ ...enabledAutomations });
|
|
3238
|
+
const [selectedAutomation, setSelectedAutomation] = useState9(null);
|
|
3239
|
+
useEffect6(() => {
|
|
3240
|
+
setLoading(true);
|
|
3241
|
+
fetchAutomationList(storeId, "default").then((data) => {
|
|
3242
|
+
setAutomations(
|
|
3243
|
+
Object.entries(data).map(([name, item]) => ({
|
|
3244
|
+
...item,
|
|
3245
|
+
automation: name,
|
|
3246
|
+
isEnabled: Boolean(enabledAutomations[name])
|
|
3247
|
+
}))
|
|
3248
|
+
);
|
|
3249
|
+
}).catch(console.error).finally(() => setLoading(false));
|
|
3250
|
+
}, [storeId, enabledAutomations]);
|
|
3251
|
+
const toggleAutomation = useCallback7((automationName) => {
|
|
3252
|
+
setAutomations(
|
|
3253
|
+
(prev) => prev.map(
|
|
3254
|
+
(a) => a.automation === automationName ? { ...a, isEnabled: !a.isEnabled } : a
|
|
3255
|
+
)
|
|
3256
|
+
);
|
|
3257
|
+
setSelectedConfigs((prev) => {
|
|
3258
|
+
const next = { ...prev };
|
|
3259
|
+
if (next[automationName]) {
|
|
3260
|
+
delete next[automationName];
|
|
3261
|
+
}
|
|
3262
|
+
return next;
|
|
3263
|
+
});
|
|
3264
|
+
}, []);
|
|
3265
|
+
const handleSave = () => {
|
|
3266
|
+
const enabledOnly = {};
|
|
3267
|
+
for (const a of automations) {
|
|
3268
|
+
if (a.isEnabled && selectedConfigs[a.automation]) {
|
|
3269
|
+
enabledOnly[a.automation] = selectedConfigs[a.automation];
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
onSubmit?.({ enabled_automations: enabledOnly });
|
|
3273
|
+
};
|
|
3274
|
+
const handleConfigClose = (automation, newConfigs) => {
|
|
3275
|
+
if (automation.isEnabled) {
|
|
3276
|
+
setSelectedConfigs((prev) => ({
|
|
3277
|
+
...prev,
|
|
3278
|
+
[automation.automation]: newConfigs
|
|
3279
|
+
}));
|
|
3280
|
+
}
|
|
3281
|
+
setSelectedAutomation(null);
|
|
3282
|
+
};
|
|
3283
|
+
if (selectedAutomation) {
|
|
3284
|
+
return /* @__PURE__ */ jsx23(
|
|
3285
|
+
AutomationConfigEditor,
|
|
3286
|
+
{
|
|
3287
|
+
storeId,
|
|
3288
|
+
automation: selectedAutomation,
|
|
3289
|
+
channelsWithVersion,
|
|
3290
|
+
initialConfigs: selectedConfigs[selectedAutomation.automation],
|
|
3291
|
+
onClose: handleConfigClose,
|
|
3292
|
+
onToggle: () => toggleAutomation(selectedAutomation.automation)
|
|
3293
|
+
}
|
|
3294
|
+
);
|
|
3295
|
+
}
|
|
3296
|
+
return /* @__PURE__ */ jsxs12("div", { className: "flex h-full flex-col gap-3 p-3", children: [
|
|
3297
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
|
|
3298
|
+
onClose && /* @__PURE__ */ jsx23(Button, { variant: "ghost", size: "icon", onClick: onClose, children: /* @__PURE__ */ jsx23(ArrowLeft3, { className: "size-4" }) }),
|
|
3299
|
+
/* @__PURE__ */ jsx23("h3", { className: "text-base font-semibold", children: "Default Store Automations" })
|
|
3300
|
+
] }),
|
|
3301
|
+
/* @__PURE__ */ jsx23(Separator2, {}),
|
|
3302
|
+
loading ? /* @__PURE__ */ jsx23("div", { className: "space-y-3 p-2", children: Array.from({ length: 5 }).map((_, i) => /* @__PURE__ */ jsx23(Skeleton, { className: "h-14 w-full rounded-md" }, i)) }) : /* @__PURE__ */ jsx23("div", { className: "flex-1 overflow-y-auto", children: /* @__PURE__ */ jsxs12(Table, { children: [
|
|
3303
|
+
/* @__PURE__ */ jsx23(TableHeader, { children: /* @__PURE__ */ jsxs12(TableRow, { children: [
|
|
3304
|
+
/* @__PURE__ */ jsx23(TableHead, { children: "Automation" }),
|
|
3305
|
+
/* @__PURE__ */ jsx23(TableHead, { className: "w-24 text-center", children: "Avg. Time" }),
|
|
3306
|
+
/* @__PURE__ */ jsx23(TableHead, { className: "w-28 text-center", children: "Credits/Prd" }),
|
|
3307
|
+
/* @__PURE__ */ jsx23(TableHead, { className: "w-28 text-center", children: "Status" }),
|
|
3308
|
+
/* @__PURE__ */ jsx23(TableHead, { className: "w-12" })
|
|
3309
|
+
] }) }),
|
|
3310
|
+
/* @__PURE__ */ jsx23(TableBody, { children: automations.map((a) => /* @__PURE__ */ jsxs12(
|
|
3311
|
+
TableRow,
|
|
3312
|
+
{
|
|
3313
|
+
className: "cursor-pointer",
|
|
3314
|
+
onClick: () => setSelectedAutomation(a),
|
|
3315
|
+
children: [
|
|
3316
|
+
/* @__PURE__ */ jsxs12(TableCell, { children: [
|
|
3317
|
+
/* @__PURE__ */ jsx23("div", { className: "text-sm font-medium", children: a.display_name }),
|
|
3318
|
+
a.description && /* @__PURE__ */ jsx23("div", { className: "text-xs text-muted-foreground", children: a.description })
|
|
3319
|
+
] }),
|
|
3320
|
+
/* @__PURE__ */ jsx23(TableCell, { className: "text-center text-xs", children: a.estimate_run_time ?? "\u2014" }),
|
|
3321
|
+
/* @__PURE__ */ jsx23(TableCell, { className: "text-center text-xs", children: a.credits ?? "\u2014" }),
|
|
3322
|
+
/* @__PURE__ */ jsx23(TableCell, { className: "text-center", children: /* @__PURE__ */ jsxs12(
|
|
3323
|
+
"div",
|
|
3324
|
+
{
|
|
3325
|
+
className: "flex items-center justify-center gap-2",
|
|
3326
|
+
onClick: (e) => e.stopPropagation(),
|
|
3327
|
+
children: [
|
|
3328
|
+
/* @__PURE__ */ jsx23(
|
|
3329
|
+
Switch,
|
|
3330
|
+
{
|
|
3331
|
+
checked: a.isEnabled,
|
|
3332
|
+
onCheckedChange: () => toggleAutomation(a.automation)
|
|
3333
|
+
}
|
|
3334
|
+
),
|
|
3335
|
+
/* @__PURE__ */ jsx23(
|
|
3336
|
+
Badge,
|
|
3337
|
+
{
|
|
3338
|
+
variant: a.isEnabled ? "default" : "secondary",
|
|
3339
|
+
className: "text-xs",
|
|
3340
|
+
children: a.isEnabled ? "Active" : "Inactive"
|
|
3341
|
+
}
|
|
3342
|
+
)
|
|
3343
|
+
]
|
|
3344
|
+
}
|
|
3345
|
+
) }),
|
|
3346
|
+
/* @__PURE__ */ jsx23(TableCell, { children: /* @__PURE__ */ jsx23(Settings, { className: "size-4 text-muted-foreground" }) })
|
|
3347
|
+
]
|
|
3348
|
+
},
|
|
3349
|
+
a.automation
|
|
3350
|
+
)) })
|
|
3351
|
+
] }) }),
|
|
3352
|
+
/* @__PURE__ */ jsx23(Separator2, {}),
|
|
3353
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex justify-end gap-2", children: [
|
|
3354
|
+
onClose && /* @__PURE__ */ jsx23(Button, { variant: "outline", onClick: onClose, children: "Cancel" }),
|
|
3355
|
+
/* @__PURE__ */ jsx23(Button, { onClick: handleSave, children: "Save Changes" })
|
|
3356
|
+
] })
|
|
3357
|
+
] });
|
|
3358
|
+
}
|
|
3359
|
+
function AutomationConfigEditor({
|
|
3360
|
+
storeId,
|
|
3361
|
+
automation,
|
|
3362
|
+
channelsWithVersion,
|
|
3363
|
+
initialConfigs,
|
|
3364
|
+
onClose,
|
|
3365
|
+
onToggle
|
|
3366
|
+
}) {
|
|
3367
|
+
const [schemaLoading, setSchemaLoading] = useState9(false);
|
|
3368
|
+
const [schema, setSchema] = useState9([]);
|
|
3369
|
+
const [configs, setConfigs] = useState9(
|
|
3370
|
+
initialConfigs ?? {}
|
|
3371
|
+
);
|
|
3372
|
+
const [options, setOptions] = useState9({});
|
|
3373
|
+
const [loadingOptions, setLoadingOptions] = useState9({});
|
|
3374
|
+
const configsRef = useRef6(configs);
|
|
3375
|
+
configsRef.current = configs;
|
|
3376
|
+
const optionsRef = useRef6(options);
|
|
3377
|
+
optionsRef.current = options;
|
|
3378
|
+
useEffect6(() => {
|
|
3379
|
+
if (!automation.isEnabled) return;
|
|
3380
|
+
setSchemaLoading(true);
|
|
3381
|
+
fetchAutomationSchema(storeId, automation.automation, "default").then((items) => {
|
|
3382
|
+
setSchema(items);
|
|
3383
|
+
if (!initialConfigs) {
|
|
3384
|
+
const defaults = {};
|
|
3385
|
+
for (const item of items) {
|
|
3386
|
+
if (item.default !== void 0) defaults[item.payload_key] = item.default;
|
|
3387
|
+
}
|
|
3388
|
+
setConfigs(defaults);
|
|
3389
|
+
configsRef.current = defaults;
|
|
3390
|
+
}
|
|
3391
|
+
}).catch(console.error).finally(() => setSchemaLoading(false));
|
|
3392
|
+
}, [storeId, automation, initialConfigs]);
|
|
3393
|
+
useEffect6(() => {
|
|
3394
|
+
if (schema.length === 0) return;
|
|
3395
|
+
for (const item of schema) {
|
|
3396
|
+
if (!item.curl) continue;
|
|
3397
|
+
if (optionsRef.current[item.payload_key]) continue;
|
|
3398
|
+
const { dependencies, allSatisfied } = getAllDependencies(
|
|
3399
|
+
item.depends,
|
|
3400
|
+
configsRef.current,
|
|
3401
|
+
channelsWithVersion
|
|
3402
|
+
);
|
|
3403
|
+
if (allSatisfied) {
|
|
3404
|
+
const url = replaceFieldsInUrl(item.curl.split(" ")[1], dependencies);
|
|
3405
|
+
setLoadingOptions((prev) => ({ ...prev, [item.payload_key]: true }));
|
|
3406
|
+
fetchDynamicLov(url).then((res) => {
|
|
3407
|
+
const data = parseLovResponse(res, item.curl, configsRef.current);
|
|
3408
|
+
if (data) {
|
|
3409
|
+
setOptions((prev) => ({ ...prev, [item.payload_key]: data }));
|
|
3410
|
+
}
|
|
3411
|
+
}).catch(console.error).finally(
|
|
3412
|
+
() => setLoadingOptions((prev) => ({
|
|
3413
|
+
...prev,
|
|
3414
|
+
[item.payload_key]: false
|
|
3415
|
+
}))
|
|
3416
|
+
);
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
}, [schema, configs, channelsWithVersion]);
|
|
3420
|
+
const handleConfigChange = useCallback7(
|
|
3421
|
+
(key, value, item) => {
|
|
3422
|
+
setConfigs((prev) => ({ ...prev, [key]: value }));
|
|
3423
|
+
if (item.linked?.length) {
|
|
3424
|
+
for (const linkedKey of item.linked) {
|
|
3425
|
+
setOptions((prev) => ({ ...prev, [linkedKey]: [] }));
|
|
3426
|
+
setConfigs((prev) => {
|
|
3427
|
+
const next = { ...prev };
|
|
3428
|
+
delete next[linkedKey];
|
|
3429
|
+
return next;
|
|
3430
|
+
});
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
},
|
|
3434
|
+
[]
|
|
3435
|
+
);
|
|
3436
|
+
return /* @__PURE__ */ jsxs12("div", { className: "flex h-full flex-col gap-3 p-3", children: [
|
|
3437
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
|
|
3438
|
+
/* @__PURE__ */ jsx23(
|
|
3439
|
+
Button,
|
|
3440
|
+
{
|
|
3441
|
+
variant: "ghost",
|
|
3442
|
+
size: "icon",
|
|
3443
|
+
onClick: () => onClose(automation, configs),
|
|
3444
|
+
children: /* @__PURE__ */ jsx23(ArrowLeft3, { className: "size-4" })
|
|
3445
|
+
}
|
|
3446
|
+
),
|
|
3447
|
+
/* @__PURE__ */ jsx23("h3", { className: "text-base font-semibold", children: automation.display_name })
|
|
3448
|
+
] }),
|
|
3449
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex gap-4 text-sm", children: [
|
|
3450
|
+
/* @__PURE__ */ jsxs12("div", { className: "text-muted-foreground", children: [
|
|
3451
|
+
"Est. time: ",
|
|
3452
|
+
/* @__PURE__ */ jsx23("span", { className: "text-foreground", children: automation.estimate_run_time ?? "\u2014" })
|
|
3453
|
+
] }),
|
|
3454
|
+
/* @__PURE__ */ jsxs12("div", { className: "text-muted-foreground", children: [
|
|
3455
|
+
"Credits/prd: ",
|
|
3456
|
+
/* @__PURE__ */ jsx23("span", { className: "text-foreground", children: automation.credits ?? "\u2014" })
|
|
3457
|
+
] })
|
|
3458
|
+
] }),
|
|
3459
|
+
/* @__PURE__ */ jsx23(Separator2, {}),
|
|
3460
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
|
|
3461
|
+
/* @__PURE__ */ jsx23("span", { className: "text-sm font-medium", children: automation.isEnabled ? "Enabled" : "Disabled" }),
|
|
3462
|
+
/* @__PURE__ */ jsx23(Switch, { checked: automation.isEnabled, onCheckedChange: onToggle })
|
|
3463
|
+
] }),
|
|
3464
|
+
/* @__PURE__ */ jsx23(Separator2, {}),
|
|
3465
|
+
/* @__PURE__ */ jsx23("div", { className: "flex-1 overflow-y-auto", children: automation.isEnabled ? schemaLoading ? /* @__PURE__ */ jsx23("div", { className: "space-y-3 p-2", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsx23(Skeleton, { className: "h-12 w-full rounded-md" }, i)) }) : /* @__PURE__ */ jsx23(
|
|
3466
|
+
SchemaFieldRenderer,
|
|
3467
|
+
{
|
|
3468
|
+
schema,
|
|
3469
|
+
configs,
|
|
3470
|
+
options,
|
|
3471
|
+
loadingOptions,
|
|
3472
|
+
onConfigChange: handleConfigChange
|
|
3473
|
+
}
|
|
3474
|
+
) : /* @__PURE__ */ jsx23("p", { className: "py-4 text-center text-sm text-muted-foreground", children: "Enable this automation to configure it." }) }),
|
|
3475
|
+
automation.preview && /* @__PURE__ */ jsxs12(Fragment5, { children: [
|
|
3476
|
+
/* @__PURE__ */ jsx23(Separator2, {}),
|
|
3477
|
+
/* @__PURE__ */ jsxs12("div", { className: "space-y-2 rounded-lg bg-muted/50 p-3 text-sm", children: [
|
|
3478
|
+
automation.preview.info_text.map((text, i) => /* @__PURE__ */ jsx23("p", { className: "text-muted-foreground", children: text }, i)),
|
|
3479
|
+
automation.preview.media.length > 0 && /* @__PURE__ */ jsx23("div", { className: "flex flex-wrap gap-2 pt-1", children: automation.preview.media.map(
|
|
3480
|
+
(m, i) => m.type === "image" ? /* @__PURE__ */ jsx23(
|
|
3481
|
+
"img",
|
|
3482
|
+
{
|
|
3483
|
+
src: m.src,
|
|
3484
|
+
alt: "",
|
|
3485
|
+
className: "max-h-24 rounded-md object-contain"
|
|
3486
|
+
},
|
|
3487
|
+
i
|
|
3488
|
+
) : null
|
|
3489
|
+
) })
|
|
3490
|
+
] })
|
|
3491
|
+
] })
|
|
3492
|
+
] });
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
// src/CatalogixChat.tsx
|
|
3496
|
+
import { jsx as jsx24, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3497
|
+
var SECTION_MAP = {
|
|
3498
|
+
"catalogix:create-store": "create_store",
|
|
3499
|
+
create_or_select_store: "create_store",
|
|
3500
|
+
"catalogix:select-products": "select_products",
|
|
3501
|
+
select_products: "select_products",
|
|
3502
|
+
"catalogix:automations": "automations",
|
|
3503
|
+
trigger_automation: "trigger_automation"
|
|
3504
|
+
};
|
|
3505
|
+
function CatalogixChat(props) {
|
|
3506
|
+
const { uiProps, meta, onSubmit, workspaceId, userId } = props;
|
|
3507
|
+
const componentName = uiProps._componentName ?? "";
|
|
3508
|
+
const section = meta?.step || SECTION_MAP[componentName] || componentName;
|
|
3509
|
+
const catalogixBaseUrl = uiProps.catalogixBaseUrl ?? "";
|
|
3510
|
+
const storeId = uiProps.storeId ?? "";
|
|
3511
|
+
const stores = uiProps.stores ?? [];
|
|
3512
|
+
const channelsWithVersion = uiProps.channelsWithVersion ?? void 0;
|
|
3513
|
+
const selectedProducts = uiProps.selectedProducts ?? [];
|
|
3514
|
+
const excludedProducts = uiProps.excludedProducts ?? [];
|
|
3515
|
+
const totalProducts = uiProps.totalProducts ?? 0;
|
|
3516
|
+
const productsCount = uiProps.productsCount ?? 0;
|
|
3517
|
+
const filters = uiProps.filters ?? {};
|
|
3518
|
+
const selectedPageRange = uiProps.selectedPageRange ?? [];
|
|
3519
|
+
const enabledAutomations = uiProps.enabledAutomations ?? {};
|
|
3520
|
+
useEffect7(() => {
|
|
3521
|
+
configureApi({ catalogixBaseUrl });
|
|
3522
|
+
}, [catalogixBaseUrl]);
|
|
3523
|
+
if (section === "select_products") {
|
|
3524
|
+
return /* @__PURE__ */ jsx24(
|
|
3525
|
+
SelectProducts,
|
|
3526
|
+
{
|
|
3527
|
+
workspaceId,
|
|
3528
|
+
storeId,
|
|
3529
|
+
userId,
|
|
3530
|
+
submitted: props.submitted,
|
|
3531
|
+
submittedValues: props.submittedValues,
|
|
3532
|
+
onConfirmSelection: async (_sel, _exc, _range, _filters, _useUuid, _payload, resumeValues) => {
|
|
3533
|
+
onSubmit(resumeValues, "Products selected.");
|
|
3534
|
+
return { submitted: true, submittedValues: resumeValues };
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
);
|
|
3538
|
+
}
|
|
3539
|
+
if (section === "create_store") {
|
|
3540
|
+
return /* @__PURE__ */ jsx24(
|
|
3541
|
+
CreateStore,
|
|
3542
|
+
{
|
|
3543
|
+
workspaceId,
|
|
3544
|
+
stores,
|
|
3545
|
+
initialStoreName: uiProps.storeName ?? "",
|
|
3546
|
+
useCatalogixTaxonomy: !!uiProps.useCatalogixTaxonomy,
|
|
3547
|
+
shouldRefetchStore: !!uiProps.shouldRefetchStore,
|
|
3548
|
+
onStoreCreated: (storeData) => onSubmit(storeData, "Store created successfully."),
|
|
3549
|
+
onCreateError: (err) => console.error("[CatalogixChat] CreateStore error:", err)
|
|
3550
|
+
}
|
|
3551
|
+
);
|
|
3552
|
+
}
|
|
3553
|
+
if (section === "trigger_automation") {
|
|
3554
|
+
return /* @__PURE__ */ jsx24(
|
|
3555
|
+
ProductAutomation,
|
|
3556
|
+
{
|
|
3557
|
+
storeId,
|
|
3558
|
+
workspaceId,
|
|
3559
|
+
channelsWithVersion,
|
|
3560
|
+
selectedProducts,
|
|
3561
|
+
excludedProducts,
|
|
3562
|
+
totalProducts,
|
|
3563
|
+
productsCount,
|
|
3564
|
+
filters,
|
|
3565
|
+
selectedPageRange,
|
|
3566
|
+
userId,
|
|
3567
|
+
onClose: () => props.onCancel?.(),
|
|
3568
|
+
onSubmit: (automationConfigs) => onSubmit(automationConfigs, "Automation applied."),
|
|
3569
|
+
onAutomationApplied: (configs) => onSubmit(configs, "Automation applied.")
|
|
3570
|
+
}
|
|
3571
|
+
);
|
|
3572
|
+
}
|
|
3573
|
+
if (section === "automations") {
|
|
3574
|
+
return /* @__PURE__ */ jsx24(
|
|
3575
|
+
StoreAutomation,
|
|
3576
|
+
{
|
|
3577
|
+
storeId,
|
|
3578
|
+
channelsWithVersion,
|
|
3579
|
+
enabledAutomations,
|
|
3580
|
+
onSubmit: (settings) => onSubmit(settings, "Store automations saved."),
|
|
3581
|
+
onClose: () => props.onCancel?.()
|
|
3582
|
+
}
|
|
3583
|
+
);
|
|
3584
|
+
}
|
|
3585
|
+
return /* @__PURE__ */ jsx24("div", { className: "p-4 text-sm text-muted-foreground", children: /* @__PURE__ */ jsxs13("p", { children: [
|
|
3586
|
+
/* @__PURE__ */ jsx24("strong", { children: "Catalogix" }),
|
|
3587
|
+
" \u2014 unknown section:",
|
|
3588
|
+
" ",
|
|
3589
|
+
/* @__PURE__ */ jsx24("code", { children: section || "(none)" })
|
|
3590
|
+
] }) });
|
|
3591
|
+
}
|
|
38
3592
|
|
|
39
3593
|
// src/manifest.ts
|
|
40
3594
|
var catalogixManifest = [
|
|
@@ -82,5 +3636,10 @@ var catalogixManifest = [
|
|
|
82
3636
|
];
|
|
83
3637
|
export {
|
|
84
3638
|
CatalogixChat,
|
|
85
|
-
|
|
3639
|
+
CreateStore,
|
|
3640
|
+
ProductAutomation,
|
|
3641
|
+
SelectProducts,
|
|
3642
|
+
StoreAutomation,
|
|
3643
|
+
catalogixManifest,
|
|
3644
|
+
configureApi
|
|
86
3645
|
};
|