@zaamx/netme-pos 0.0.10 → 0.0.12
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/.medusa/server/src/admin/index.js +1341 -10
- package/.medusa/server/src/admin/index.mjs +1344 -13
- package/.medusa/server/src/api/admin/payment-options/[id]/route.js +70 -0
- package/.medusa/server/src/api/admin/payment-options/route.js +84 -0
- package/.medusa/server/src/api/admin/pos-permissions/[id]/route.js +71 -0
- package/.medusa/server/src/api/admin/pos-permissions/options/route.js +20 -0
- package/package.json +1 -1
|
@@ -6,6 +6,7 @@ const ui = require("@medusajs/ui");
|
|
|
6
6
|
const react = require("react");
|
|
7
7
|
const reactQuery = require("@tanstack/react-query");
|
|
8
8
|
const Medusa = require("@medusajs/js-sdk");
|
|
9
|
+
const reactRouterDom = require("react-router-dom");
|
|
9
10
|
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
10
11
|
const Medusa__default = /* @__PURE__ */ _interopDefault(Medusa);
|
|
11
12
|
const sdk = new Medusa__default.default({
|
|
@@ -166,6 +167,7 @@ const DeleteOperatorModal = ({
|
|
|
166
167
|
const OperatorActions = ({ operator }) => {
|
|
167
168
|
const [editOpen, setEditOpen] = react.useState(false);
|
|
168
169
|
const [deleteOpen, setDeleteOpen] = react.useState(false);
|
|
170
|
+
const navigate = reactRouterDom.useNavigate();
|
|
169
171
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
170
172
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
|
|
171
173
|
/* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { size: "small", variant: "transparent", children: /* @__PURE__ */ jsxRuntime.jsx(icons.EllipsisHorizontal, {}) }) }),
|
|
@@ -174,6 +176,17 @@ const OperatorActions = ({ operator }) => {
|
|
|
174
176
|
/* @__PURE__ */ jsxRuntime.jsx(icons.PencilSquare, { className: "text-ui-fg-subtle" }),
|
|
175
177
|
"Edit"
|
|
176
178
|
] }),
|
|
179
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
180
|
+
ui.DropdownMenu.Item,
|
|
181
|
+
{
|
|
182
|
+
onClick: () => navigate(`/pos-permissions/${operator.id}`),
|
|
183
|
+
className: "gap-x-2",
|
|
184
|
+
children: [
|
|
185
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.ShieldCheck, { className: "text-ui-fg-subtle" }),
|
|
186
|
+
"Permisos POS"
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
),
|
|
177
190
|
/* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Separator, {}),
|
|
178
191
|
/* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Item, { onClick: () => setDeleteOpen(true), className: "gap-x-2", children: [
|
|
179
192
|
/* @__PURE__ */ jsxRuntime.jsx(icons.Trash, { className: "text-ui-fg-subtle" }),
|
|
@@ -311,24 +324,24 @@ const CreateOperatorModal = () => {
|
|
|
311
324
|
] }) }) })
|
|
312
325
|
] });
|
|
313
326
|
};
|
|
314
|
-
const columnHelper = ui.createDataTableColumnHelper();
|
|
315
|
-
const useColumns = () => {
|
|
327
|
+
const columnHelper$2 = ui.createDataTableColumnHelper();
|
|
328
|
+
const useColumns$2 = () => {
|
|
316
329
|
return react.useMemo(
|
|
317
330
|
() => [
|
|
318
|
-
columnHelper.accessor("first_name", {
|
|
331
|
+
columnHelper$2.accessor("first_name", {
|
|
319
332
|
header: "First Name"
|
|
320
333
|
}),
|
|
321
|
-
columnHelper.accessor("last_name", {
|
|
334
|
+
columnHelper$2.accessor("last_name", {
|
|
322
335
|
header: "Last Name"
|
|
323
336
|
}),
|
|
324
|
-
columnHelper.accessor("email", {
|
|
337
|
+
columnHelper$2.accessor("email", {
|
|
325
338
|
header: "Email"
|
|
326
339
|
}),
|
|
327
|
-
columnHelper.accessor("created_at", {
|
|
340
|
+
columnHelper$2.accessor("created_at", {
|
|
328
341
|
header: "Date Added",
|
|
329
342
|
cell: ({ getValue }) => new Date(getValue()).toLocaleDateString()
|
|
330
343
|
}),
|
|
331
|
-
columnHelper.display({
|
|
344
|
+
columnHelper$2.display({
|
|
332
345
|
id: "actions",
|
|
333
346
|
cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsx(OperatorActions, { operator: row.original })
|
|
334
347
|
})
|
|
@@ -355,7 +368,7 @@ const OperatorTable = () => {
|
|
|
355
368
|
queryKey: ["operators", limit, offset, searchValue],
|
|
356
369
|
placeholderData: (previousData) => previousData
|
|
357
370
|
});
|
|
358
|
-
const columns = useColumns();
|
|
371
|
+
const columns = useColumns$2();
|
|
359
372
|
const table = ui.useDataTable({
|
|
360
373
|
data: (data == null ? void 0 : data.operators) || [],
|
|
361
374
|
columns,
|
|
@@ -386,10 +399,1300 @@ const OperatorsPage = () => {
|
|
|
386
399
|
/* @__PURE__ */ jsxRuntime.jsx(OperatorTable, {})
|
|
387
400
|
] });
|
|
388
401
|
};
|
|
389
|
-
const config = adminSdk.defineRouteConfig({
|
|
402
|
+
const config$2 = adminSdk.defineRouteConfig({
|
|
390
403
|
label: "Operators",
|
|
391
404
|
icon: icons.Users
|
|
392
405
|
});
|
|
406
|
+
const STATUS_OPTIONS = [
|
|
407
|
+
"pending",
|
|
408
|
+
"paid",
|
|
409
|
+
"approved",
|
|
410
|
+
"rejected",
|
|
411
|
+
"canceled"
|
|
412
|
+
];
|
|
413
|
+
const toForm = (po) => ({
|
|
414
|
+
payment_method: po.payment_method ?? "",
|
|
415
|
+
amount: po.amount != null ? String(po.amount) : "",
|
|
416
|
+
status: po.status ?? "",
|
|
417
|
+
concept: po.concept ?? "",
|
|
418
|
+
reference_code: po.reference_code ?? "",
|
|
419
|
+
institution: po.institution ?? "",
|
|
420
|
+
depositor_name: po.depositor_name ?? "",
|
|
421
|
+
terminal_reference: po.terminal_reference ?? "",
|
|
422
|
+
ticket_amount: po.ticket_amount != null ? String(po.ticket_amount) : "",
|
|
423
|
+
authorizer: po.authorizer ?? "",
|
|
424
|
+
receipt_file: po.receipt_file ?? ""
|
|
425
|
+
});
|
|
426
|
+
const EditPaymentOptionDrawer = ({
|
|
427
|
+
paymentOption,
|
|
428
|
+
open,
|
|
429
|
+
setOpen
|
|
430
|
+
}) => {
|
|
431
|
+
const [formData, setFormData] = react.useState(toForm(paymentOption));
|
|
432
|
+
const queryClient = reactQuery.useQueryClient();
|
|
433
|
+
react.useEffect(() => {
|
|
434
|
+
if (open) {
|
|
435
|
+
setFormData(toForm(paymentOption));
|
|
436
|
+
}
|
|
437
|
+
}, [open, paymentOption]);
|
|
438
|
+
const update = (key, value) => setFormData((prev) => ({ ...prev, [key]: value }));
|
|
439
|
+
const updateMutation = reactQuery.useMutation({
|
|
440
|
+
mutationFn: () => {
|
|
441
|
+
const payload = {
|
|
442
|
+
payment_method: formData.payment_method,
|
|
443
|
+
status: formData.status,
|
|
444
|
+
concept: formData.concept,
|
|
445
|
+
reference_code: formData.reference_code,
|
|
446
|
+
institution: formData.institution,
|
|
447
|
+
depositor_name: formData.depositor_name,
|
|
448
|
+
terminal_reference: formData.terminal_reference,
|
|
449
|
+
authorizer: formData.authorizer,
|
|
450
|
+
receipt_file: formData.receipt_file
|
|
451
|
+
};
|
|
452
|
+
if (formData.amount.trim() !== "") {
|
|
453
|
+
payload.amount = Number(formData.amount);
|
|
454
|
+
}
|
|
455
|
+
if (formData.ticket_amount.trim() !== "") {
|
|
456
|
+
payload.ticket_amount = Number(formData.ticket_amount);
|
|
457
|
+
}
|
|
458
|
+
return sdk.client.fetch(
|
|
459
|
+
`/admin/payment-options/${paymentOption.id}`,
|
|
460
|
+
{
|
|
461
|
+
method: "POST",
|
|
462
|
+
body: payload
|
|
463
|
+
}
|
|
464
|
+
);
|
|
465
|
+
},
|
|
466
|
+
onSuccess: () => {
|
|
467
|
+
queryClient.invalidateQueries({ queryKey: ["payment-options"] });
|
|
468
|
+
ui.toast.success("Nota de pago actualizada");
|
|
469
|
+
setOpen(false);
|
|
470
|
+
},
|
|
471
|
+
onError: (error) => {
|
|
472
|
+
ui.toast.error(error.message || "No se pudo actualizar la nota de pago");
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
const statusOptions = STATUS_OPTIONS.includes(formData.status) || !formData.status ? STATUS_OPTIONS : [formData.status, ...STATUS_OPTIONS];
|
|
476
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Drawer.Content, { children: [
|
|
477
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Title, { children: "Editar nota de pago" }) }),
|
|
478
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Body, { className: "flex-1 overflow-auto p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-4", children: [
|
|
479
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [
|
|
480
|
+
paymentOption.order_display_id ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", leading: "compact", className: "text-ui-fg-subtle", children: [
|
|
481
|
+
"Orden #",
|
|
482
|
+
paymentOption.order_display_id
|
|
483
|
+
] }) : null,
|
|
484
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", leading: "compact", className: "text-ui-fg-subtle", children: [
|
|
485
|
+
"Pago: ",
|
|
486
|
+
paymentOption.payment_id
|
|
487
|
+
] })
|
|
488
|
+
] }),
|
|
489
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
490
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Método de pago" }),
|
|
491
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
492
|
+
ui.Input,
|
|
493
|
+
{
|
|
494
|
+
value: formData.payment_method,
|
|
495
|
+
onChange: (e) => update("payment_method", e.target.value)
|
|
496
|
+
}
|
|
497
|
+
)
|
|
498
|
+
] }),
|
|
499
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-x-3", children: [
|
|
500
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
501
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Monto" }),
|
|
502
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
503
|
+
ui.Input,
|
|
504
|
+
{
|
|
505
|
+
type: "number",
|
|
506
|
+
step: "0.01",
|
|
507
|
+
value: formData.amount,
|
|
508
|
+
onChange: (e) => update("amount", e.target.value)
|
|
509
|
+
}
|
|
510
|
+
)
|
|
511
|
+
] }),
|
|
512
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
513
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Estatus" }),
|
|
514
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
515
|
+
ui.Select,
|
|
516
|
+
{
|
|
517
|
+
value: formData.status,
|
|
518
|
+
onValueChange: (value) => update("status", value),
|
|
519
|
+
children: [
|
|
520
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Seleccionar estatus" }) }),
|
|
521
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Content, { children: statusOptions.map((s) => /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: s, children: s }, s)) })
|
|
522
|
+
]
|
|
523
|
+
}
|
|
524
|
+
)
|
|
525
|
+
] })
|
|
526
|
+
] }),
|
|
527
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
528
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Concepto" }),
|
|
529
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
530
|
+
ui.Input,
|
|
531
|
+
{
|
|
532
|
+
value: formData.concept,
|
|
533
|
+
onChange: (e) => update("concept", e.target.value)
|
|
534
|
+
}
|
|
535
|
+
)
|
|
536
|
+
] }),
|
|
537
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
538
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Folio / Referencia" }),
|
|
539
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
540
|
+
ui.Input,
|
|
541
|
+
{
|
|
542
|
+
value: formData.reference_code,
|
|
543
|
+
onChange: (e) => update("reference_code", e.target.value)
|
|
544
|
+
}
|
|
545
|
+
)
|
|
546
|
+
] }),
|
|
547
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
548
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Institución" }),
|
|
549
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
550
|
+
ui.Input,
|
|
551
|
+
{
|
|
552
|
+
value: formData.institution,
|
|
553
|
+
onChange: (e) => update("institution", e.target.value)
|
|
554
|
+
}
|
|
555
|
+
)
|
|
556
|
+
] }),
|
|
557
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
558
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Nombre del depositante" }),
|
|
559
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
560
|
+
ui.Input,
|
|
561
|
+
{
|
|
562
|
+
value: formData.depositor_name,
|
|
563
|
+
onChange: (e) => update("depositor_name", e.target.value)
|
|
564
|
+
}
|
|
565
|
+
)
|
|
566
|
+
] }),
|
|
567
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-x-3", children: [
|
|
568
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
569
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Referencia de terminal" }),
|
|
570
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
571
|
+
ui.Input,
|
|
572
|
+
{
|
|
573
|
+
value: formData.terminal_reference,
|
|
574
|
+
onChange: (e) => update("terminal_reference", e.target.value)
|
|
575
|
+
}
|
|
576
|
+
)
|
|
577
|
+
] }),
|
|
578
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
579
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Monto del ticket" }),
|
|
580
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
581
|
+
ui.Input,
|
|
582
|
+
{
|
|
583
|
+
type: "number",
|
|
584
|
+
step: "0.01",
|
|
585
|
+
value: formData.ticket_amount,
|
|
586
|
+
onChange: (e) => update("ticket_amount", e.target.value)
|
|
587
|
+
}
|
|
588
|
+
)
|
|
589
|
+
] })
|
|
590
|
+
] }),
|
|
591
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
592
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Autorizó" }),
|
|
593
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
594
|
+
ui.Input,
|
|
595
|
+
{
|
|
596
|
+
value: formData.authorizer,
|
|
597
|
+
onChange: (e) => update("authorizer", e.target.value)
|
|
598
|
+
}
|
|
599
|
+
)
|
|
600
|
+
] }),
|
|
601
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
602
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { children: "Comprobante (URL)" }),
|
|
603
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
604
|
+
ui.Input,
|
|
605
|
+
{
|
|
606
|
+
value: formData.receipt_file,
|
|
607
|
+
onChange: (e) => update("receipt_file", e.target.value)
|
|
608
|
+
}
|
|
609
|
+
),
|
|
610
|
+
formData.receipt_file && /* @__PURE__ */ jsxRuntime.jsx(
|
|
611
|
+
"a",
|
|
612
|
+
{
|
|
613
|
+
href: formData.receipt_file,
|
|
614
|
+
target: "_blank",
|
|
615
|
+
rel: "noreferrer",
|
|
616
|
+
className: "text-ui-fg-interactive text-xs",
|
|
617
|
+
children: "Ver comprobante"
|
|
618
|
+
}
|
|
619
|
+
)
|
|
620
|
+
] })
|
|
621
|
+
] }) }),
|
|
622
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Footer, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-end gap-x-2", children: [
|
|
623
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Drawer.Close, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
624
|
+
ui.Button,
|
|
625
|
+
{
|
|
626
|
+
size: "small",
|
|
627
|
+
variant: "secondary",
|
|
628
|
+
disabled: updateMutation.isPending,
|
|
629
|
+
children: "Cancelar"
|
|
630
|
+
}
|
|
631
|
+
) }),
|
|
632
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
633
|
+
ui.Button,
|
|
634
|
+
{
|
|
635
|
+
size: "small",
|
|
636
|
+
onClick: () => updateMutation.mutate(),
|
|
637
|
+
isLoading: updateMutation.isPending,
|
|
638
|
+
children: "Guardar"
|
|
639
|
+
}
|
|
640
|
+
)
|
|
641
|
+
] }) })
|
|
642
|
+
] }) });
|
|
643
|
+
};
|
|
644
|
+
const DeletePaymentOptionModal = ({
|
|
645
|
+
paymentOption,
|
|
646
|
+
open,
|
|
647
|
+
setOpen
|
|
648
|
+
}) => {
|
|
649
|
+
const queryClient = reactQuery.useQueryClient();
|
|
650
|
+
const deleteMutation = reactQuery.useMutation({
|
|
651
|
+
mutationFn: () => sdk.client.fetch(`/admin/payment-options/${paymentOption.id}`, {
|
|
652
|
+
method: "DELETE"
|
|
653
|
+
}),
|
|
654
|
+
onSuccess: () => {
|
|
655
|
+
queryClient.invalidateQueries({ queryKey: ["payment-options"] });
|
|
656
|
+
ui.toast.success("Nota de pago eliminada");
|
|
657
|
+
setOpen(false);
|
|
658
|
+
},
|
|
659
|
+
onError: (error) => {
|
|
660
|
+
ui.toast.error(error.message || "No se pudo eliminar la nota de pago");
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
const label = paymentOption.reference_code || paymentOption.concept || paymentOption.payment_method || paymentOption.id;
|
|
664
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Prompt, { open, onOpenChange: setOpen, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Prompt.Content, { children: [
|
|
665
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Prompt.Header, { children: [
|
|
666
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Title, { children: "Eliminar nota de pago" }),
|
|
667
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Prompt.Description, { children: [
|
|
668
|
+
'¿Seguro que deseas eliminar la nota de pago "',
|
|
669
|
+
label,
|
|
670
|
+
'"? Esta acción no se puede deshacer.'
|
|
671
|
+
] })
|
|
672
|
+
] }),
|
|
673
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Prompt.Footer, { children: [
|
|
674
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Prompt.Cancel, { children: "Cancelar" }),
|
|
675
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
676
|
+
ui.Prompt.Action,
|
|
677
|
+
{
|
|
678
|
+
onClick: () => deleteMutation.mutate(),
|
|
679
|
+
disabled: deleteMutation.isPending,
|
|
680
|
+
children: "Eliminar"
|
|
681
|
+
}
|
|
682
|
+
)
|
|
683
|
+
] })
|
|
684
|
+
] }) });
|
|
685
|
+
};
|
|
686
|
+
const PaymentOptionActions = ({
|
|
687
|
+
paymentOption
|
|
688
|
+
}) => {
|
|
689
|
+
const [editOpen, setEditOpen] = react.useState(false);
|
|
690
|
+
const [deleteOpen, setDeleteOpen] = react.useState(false);
|
|
691
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
692
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
|
|
693
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(ui.IconButton, { size: "small", variant: "transparent", children: /* @__PURE__ */ jsxRuntime.jsx(icons.EllipsisHorizontal, {}) }) }),
|
|
694
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu.Content, { children: [
|
|
695
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
696
|
+
ui.DropdownMenu.Item,
|
|
697
|
+
{
|
|
698
|
+
onClick: () => setEditOpen(true),
|
|
699
|
+
className: "gap-x-2",
|
|
700
|
+
children: [
|
|
701
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.PencilSquare, { className: "text-ui-fg-subtle" }),
|
|
702
|
+
"Editar"
|
|
703
|
+
]
|
|
704
|
+
}
|
|
705
|
+
),
|
|
706
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenu.Separator, {}),
|
|
707
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
708
|
+
ui.DropdownMenu.Item,
|
|
709
|
+
{
|
|
710
|
+
onClick: () => setDeleteOpen(true),
|
|
711
|
+
className: "gap-x-2",
|
|
712
|
+
children: [
|
|
713
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.Trash, { className: "text-ui-fg-subtle" }),
|
|
714
|
+
"Eliminar"
|
|
715
|
+
]
|
|
716
|
+
}
|
|
717
|
+
)
|
|
718
|
+
] })
|
|
719
|
+
] }),
|
|
720
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
721
|
+
EditPaymentOptionDrawer,
|
|
722
|
+
{
|
|
723
|
+
paymentOption,
|
|
724
|
+
open: editOpen,
|
|
725
|
+
setOpen: setEditOpen
|
|
726
|
+
}
|
|
727
|
+
),
|
|
728
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
729
|
+
DeletePaymentOptionModal,
|
|
730
|
+
{
|
|
731
|
+
paymentOption,
|
|
732
|
+
open: deleteOpen,
|
|
733
|
+
setOpen: setDeleteOpen
|
|
734
|
+
}
|
|
735
|
+
)
|
|
736
|
+
] });
|
|
737
|
+
};
|
|
738
|
+
const columnHelper$1 = ui.createDataTableColumnHelper();
|
|
739
|
+
const formatAmount = (value) => {
|
|
740
|
+
const n = Number(value ?? 0);
|
|
741
|
+
return n.toLocaleString("es-MX", {
|
|
742
|
+
minimumFractionDigits: 2,
|
|
743
|
+
maximumFractionDigits: 2
|
|
744
|
+
});
|
|
745
|
+
};
|
|
746
|
+
const statusColor = (status) => {
|
|
747
|
+
switch ((status || "").toLowerCase()) {
|
|
748
|
+
case "paid":
|
|
749
|
+
case "pagado":
|
|
750
|
+
case "approved":
|
|
751
|
+
case "aprobado":
|
|
752
|
+
case "completed":
|
|
753
|
+
return "green";
|
|
754
|
+
case "pending":
|
|
755
|
+
case "pendiente":
|
|
756
|
+
case "in_progress":
|
|
757
|
+
return "orange";
|
|
758
|
+
case "rejected":
|
|
759
|
+
case "rechazado":
|
|
760
|
+
case "failed":
|
|
761
|
+
case "canceled":
|
|
762
|
+
case "cancelled":
|
|
763
|
+
case "cancelado":
|
|
764
|
+
return "red";
|
|
765
|
+
default:
|
|
766
|
+
return "grey";
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
const useColumns$1 = () => {
|
|
770
|
+
return react.useMemo(
|
|
771
|
+
() => [
|
|
772
|
+
columnHelper$1.accessor("order_display_id", {
|
|
773
|
+
header: "Orden",
|
|
774
|
+
cell: ({ row }) => {
|
|
775
|
+
const displayId = row.original.order_display_id;
|
|
776
|
+
const orderId = row.original.order_id;
|
|
777
|
+
if (!displayId) {
|
|
778
|
+
return "—";
|
|
779
|
+
}
|
|
780
|
+
if (!orderId) {
|
|
781
|
+
return `#${displayId}`;
|
|
782
|
+
}
|
|
783
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
784
|
+
"a",
|
|
785
|
+
{
|
|
786
|
+
href: `/app/orders/${orderId}`,
|
|
787
|
+
className: "text-ui-fg-interactive hover:underline",
|
|
788
|
+
onClick: (e) => e.stopPropagation(),
|
|
789
|
+
children: [
|
|
790
|
+
"#",
|
|
791
|
+
displayId
|
|
792
|
+
]
|
|
793
|
+
}
|
|
794
|
+
);
|
|
795
|
+
}
|
|
796
|
+
}),
|
|
797
|
+
columnHelper$1.accessor("reference_code", {
|
|
798
|
+
header: "Folio / Referencia",
|
|
799
|
+
cell: ({ getValue }) => getValue() || "—"
|
|
800
|
+
}),
|
|
801
|
+
columnHelper$1.accessor("payment_method", {
|
|
802
|
+
header: "Método",
|
|
803
|
+
cell: ({ getValue }) => getValue() || "—"
|
|
804
|
+
}),
|
|
805
|
+
columnHelper$1.accessor("amount", {
|
|
806
|
+
header: "Monto",
|
|
807
|
+
cell: ({ getValue }) => formatAmount(getValue())
|
|
808
|
+
}),
|
|
809
|
+
columnHelper$1.accessor("status", {
|
|
810
|
+
header: "Estatus",
|
|
811
|
+
cell: ({ getValue }) => {
|
|
812
|
+
const status = getValue();
|
|
813
|
+
if (!status) {
|
|
814
|
+
return "—";
|
|
815
|
+
}
|
|
816
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: statusColor(status), children: status });
|
|
817
|
+
}
|
|
818
|
+
}),
|
|
819
|
+
columnHelper$1.accessor("concept", {
|
|
820
|
+
header: "Concepto",
|
|
821
|
+
cell: ({ getValue }) => getValue() || "—"
|
|
822
|
+
}),
|
|
823
|
+
columnHelper$1.accessor("created_at", {
|
|
824
|
+
header: "Fecha",
|
|
825
|
+
cell: ({ getValue }) => new Date(getValue()).toLocaleString()
|
|
826
|
+
}),
|
|
827
|
+
columnHelper$1.display({
|
|
828
|
+
id: "actions",
|
|
829
|
+
cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsx(PaymentOptionActions, { paymentOption: row.original })
|
|
830
|
+
})
|
|
831
|
+
],
|
|
832
|
+
[]
|
|
833
|
+
);
|
|
834
|
+
};
|
|
835
|
+
const PaymentOptionTable = () => {
|
|
836
|
+
const [searchValue, setSearchValue] = react.useState("");
|
|
837
|
+
const [pagination, setPagination] = react.useState({
|
|
838
|
+
pageIndex: 0,
|
|
839
|
+
pageSize: 15
|
|
840
|
+
});
|
|
841
|
+
const limit = pagination.pageSize;
|
|
842
|
+
const offset = pagination.pageIndex * limit;
|
|
843
|
+
const { data, isLoading } = reactQuery.useQuery({
|
|
844
|
+
queryFn: () => sdk.client.fetch("/admin/payment-options", {
|
|
845
|
+
query: {
|
|
846
|
+
limit,
|
|
847
|
+
offset,
|
|
848
|
+
q: searchValue || void 0
|
|
849
|
+
}
|
|
850
|
+
}),
|
|
851
|
+
queryKey: ["payment-options", limit, offset, searchValue],
|
|
852
|
+
placeholderData: (previousData) => previousData
|
|
853
|
+
});
|
|
854
|
+
const columns = useColumns$1();
|
|
855
|
+
const table = ui.useDataTable({
|
|
856
|
+
data: (data == null ? void 0 : data.payment_options) || [],
|
|
857
|
+
columns,
|
|
858
|
+
getRowId: (row) => row.id,
|
|
859
|
+
rowCount: (data == null ? void 0 : data.count) || 0,
|
|
860
|
+
isLoading,
|
|
861
|
+
search: {
|
|
862
|
+
state: searchValue,
|
|
863
|
+
onSearchChange: setSearchValue
|
|
864
|
+
},
|
|
865
|
+
pagination: {
|
|
866
|
+
state: pagination,
|
|
867
|
+
onPaginationChange: setPagination
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.DataTable, { instance: table, children: [
|
|
871
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Toolbar, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-full items-center justify-between gap-x-2", children: [
|
|
872
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
|
|
873
|
+
(data == null ? void 0 : data.count) ?? 0,
|
|
874
|
+
" notas"
|
|
875
|
+
] }),
|
|
876
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Search, { placeholder: "Buscar por orden, folio, concepto, método..." })
|
|
877
|
+
] }) }),
|
|
878
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Table, {}),
|
|
879
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Pagination, {})
|
|
880
|
+
] });
|
|
881
|
+
};
|
|
882
|
+
const PaymentOptionsPage = () => {
|
|
883
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "flex flex-col items-stretch overflow-hidden p-0", children: [
|
|
884
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between border-b border-ui-border-base px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [
|
|
885
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Notas de pago (POS)" }),
|
|
886
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Revisa, edita o elimina las notas de pago registradas desde el POS." })
|
|
887
|
+
] }) }),
|
|
888
|
+
/* @__PURE__ */ jsxRuntime.jsx(PaymentOptionTable, {})
|
|
889
|
+
] });
|
|
890
|
+
};
|
|
891
|
+
const config$1 = adminSdk.defineRouteConfig({
|
|
892
|
+
label: "Notas de pago",
|
|
893
|
+
icon: icons.CurrencyDollar
|
|
894
|
+
});
|
|
895
|
+
const POS_ROLES = [
|
|
896
|
+
{ value: "cashier", label: "Cajero" },
|
|
897
|
+
{ value: "supervisor", label: "Supervisor" },
|
|
898
|
+
{ value: "manager", label: "Gerente" }
|
|
899
|
+
];
|
|
900
|
+
const CAPABILITY_GROUPS = [
|
|
901
|
+
{
|
|
902
|
+
title: "Venta / carrito",
|
|
903
|
+
items: [
|
|
904
|
+
{ key: "cancel_sale", label: "Cancelar venta", description: "Botón que borra el draft" },
|
|
905
|
+
{ key: "pause_sale", label: "Pausar venta", description: "Guardar para después" },
|
|
906
|
+
{ key: "remove_item", label: "Eliminar ítems del carrito" },
|
|
907
|
+
{ key: "edit_quantity", label: "Cambiar cantidades" },
|
|
908
|
+
{ key: "apply_promotion", label: "Aplicar / quitar cupón" },
|
|
909
|
+
{ key: "apply_manual_discount", label: "Descuento manual (override de precio)", description: "Acción sensible" }
|
|
910
|
+
]
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
title: "Configuración de la sesión",
|
|
914
|
+
items: [
|
|
915
|
+
{ key: "change_region", label: "Cambiar región" },
|
|
916
|
+
{ key: "change_sales_channel", label: "Cambiar canal de venta" },
|
|
917
|
+
{ key: "change_location", label: "Cambiar ubicación de surtido" }
|
|
918
|
+
]
|
|
919
|
+
},
|
|
920
|
+
{
|
|
921
|
+
title: "Pago",
|
|
922
|
+
items: [
|
|
923
|
+
{ key: "pay_cash", label: "Cobrar en caja (efectivo)" },
|
|
924
|
+
{ key: "pay_card", label: "Cobrar con tarjeta (Stripe)" },
|
|
925
|
+
{ key: "use_store_credit", label: "Pagar con Monedero (store credit)" },
|
|
926
|
+
{ key: "capture_payment", label: "Capturar pago" },
|
|
927
|
+
{ key: "capture_without_docs", label: "Capturar sin documentación", description: "Acción sensible" }
|
|
928
|
+
]
|
|
929
|
+
},
|
|
930
|
+
{
|
|
931
|
+
title: "Envío / dirección",
|
|
932
|
+
items: [
|
|
933
|
+
{ key: "edit_shipping", label: "Elegir / cambiar método de envío" },
|
|
934
|
+
{ key: "edit_customer_address", label: "Editar dirección de envío del cliente" }
|
|
935
|
+
]
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
title: "Ventas pendientes",
|
|
939
|
+
items: [
|
|
940
|
+
{ key: "view_pending_drafts", label: "Ver ventas pendientes (/pos-drafts)" },
|
|
941
|
+
{ key: "recover_draft", label: "Recuperar / cargar un draft" }
|
|
942
|
+
]
|
|
943
|
+
}
|
|
944
|
+
];
|
|
945
|
+
const CAPABILITY_KEYS = CAPABILITY_GROUPS.flatMap(
|
|
946
|
+
(g) => g.items.map((i) => i.key)
|
|
947
|
+
);
|
|
948
|
+
const allTrue = () => CAPABILITY_KEYS.reduce(
|
|
949
|
+
(acc, key) => ({ ...acc, [key]: true }),
|
|
950
|
+
{}
|
|
951
|
+
);
|
|
952
|
+
const ROLE_PRESETS = {
|
|
953
|
+
cashier: {
|
|
954
|
+
...allTrue(),
|
|
955
|
+
cancel_sale: false,
|
|
956
|
+
apply_manual_discount: false,
|
|
957
|
+
change_region: false,
|
|
958
|
+
change_sales_channel: false,
|
|
959
|
+
capture_payment: false,
|
|
960
|
+
capture_without_docs: false,
|
|
961
|
+
view_pending_drafts: false,
|
|
962
|
+
recover_draft: false
|
|
963
|
+
},
|
|
964
|
+
supervisor: {
|
|
965
|
+
...allTrue(),
|
|
966
|
+
capture_without_docs: false
|
|
967
|
+
},
|
|
968
|
+
manager: {
|
|
969
|
+
...allTrue()
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
const presetForRole = (role) => {
|
|
973
|
+
if (role && ROLE_PRESETS[role]) {
|
|
974
|
+
return ROLE_PRESETS[role];
|
|
975
|
+
}
|
|
976
|
+
return allTrue();
|
|
977
|
+
};
|
|
978
|
+
const resolveCapabilities = (role, stored) => {
|
|
979
|
+
const preset = presetForRole(role);
|
|
980
|
+
return CAPABILITY_KEYS.reduce((acc, key) => {
|
|
981
|
+
const storedValue = stored && typeof stored[key] === "boolean" ? stored[key] : void 0;
|
|
982
|
+
acc[key] = storedValue ?? preset[key] ?? true;
|
|
983
|
+
return acc;
|
|
984
|
+
}, {});
|
|
985
|
+
};
|
|
986
|
+
const columnHelper = ui.createDataTableColumnHelper();
|
|
987
|
+
const roleLabel = (role) => {
|
|
988
|
+
var _a;
|
|
989
|
+
return ((_a = POS_ROLES.find((r) => r.value === role)) == null ? void 0 : _a.label) || null;
|
|
990
|
+
};
|
|
991
|
+
const scopeSummary = (ids) => Array.isArray(ids) ? `${ids.length} seleccionada${ids.length === 1 ? "" : "s"}` : "Todas";
|
|
992
|
+
const useColumns = () => react.useMemo(
|
|
993
|
+
() => [
|
|
994
|
+
columnHelper.accessor("first_name", {
|
|
995
|
+
header: "Nombre",
|
|
996
|
+
cell: ({ row }) => `${row.original.first_name ?? ""} ${row.original.last_name ?? ""}`.trim() || "—"
|
|
997
|
+
}),
|
|
998
|
+
columnHelper.accessor("email", {
|
|
999
|
+
header: "Email"
|
|
1000
|
+
}),
|
|
1001
|
+
columnHelper.display({
|
|
1002
|
+
id: "role",
|
|
1003
|
+
header: "Rol POS",
|
|
1004
|
+
cell: ({ row }) => {
|
|
1005
|
+
var _a;
|
|
1006
|
+
const label = roleLabel((_a = row.original.metadata) == null ? void 0 : _a.pos_role);
|
|
1007
|
+
return label ? /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "blue", children: label }) : /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted", children: "Sin rol" });
|
|
1008
|
+
}
|
|
1009
|
+
}),
|
|
1010
|
+
columnHelper.display({
|
|
1011
|
+
id: "locations",
|
|
1012
|
+
header: "Ubicaciones",
|
|
1013
|
+
cell: ({ row }) => {
|
|
1014
|
+
var _a;
|
|
1015
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", children: scopeSummary((_a = row.original.metadata) == null ? void 0 : _a.pos_stock_location_ids) });
|
|
1016
|
+
}
|
|
1017
|
+
}),
|
|
1018
|
+
columnHelper.display({
|
|
1019
|
+
id: "actions",
|
|
1020
|
+
cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { asChild: true, size: "small", variant: "secondary", children: /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { to: `/pos-permissions/${row.original.id}`, children: "Editar permisos" }) }) })
|
|
1021
|
+
})
|
|
1022
|
+
],
|
|
1023
|
+
[]
|
|
1024
|
+
);
|
|
1025
|
+
const PosPermissionsPage = () => {
|
|
1026
|
+
const [searchValue, setSearchValue] = react.useState("");
|
|
1027
|
+
const [pagination, setPagination] = react.useState({
|
|
1028
|
+
pageIndex: 0,
|
|
1029
|
+
pageSize: 15
|
|
1030
|
+
});
|
|
1031
|
+
const limit = pagination.pageSize;
|
|
1032
|
+
const offset = pagination.pageIndex * limit;
|
|
1033
|
+
const { data, isLoading } = reactQuery.useQuery({
|
|
1034
|
+
queryFn: () => sdk.client.fetch("/admin/operators", {
|
|
1035
|
+
query: {
|
|
1036
|
+
limit,
|
|
1037
|
+
offset,
|
|
1038
|
+
q: searchValue || void 0
|
|
1039
|
+
}
|
|
1040
|
+
}),
|
|
1041
|
+
queryKey: ["operators", "pos-permissions", limit, offset, searchValue],
|
|
1042
|
+
placeholderData: (previousData) => previousData
|
|
1043
|
+
});
|
|
1044
|
+
const columns = useColumns();
|
|
1045
|
+
const table = ui.useDataTable({
|
|
1046
|
+
data: (data == null ? void 0 : data.operators) || [],
|
|
1047
|
+
columns,
|
|
1048
|
+
getRowId: (row) => row.id,
|
|
1049
|
+
rowCount: (data == null ? void 0 : data.count) || 0,
|
|
1050
|
+
isLoading,
|
|
1051
|
+
search: {
|
|
1052
|
+
state: searchValue,
|
|
1053
|
+
onSearchChange: setSearchValue
|
|
1054
|
+
},
|
|
1055
|
+
pagination: {
|
|
1056
|
+
state: pagination,
|
|
1057
|
+
onPaginationChange: setPagination
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "flex flex-col items-stretch overflow-hidden p-0", children: [
|
|
1061
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between border-b border-ui-border-base px-6 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [
|
|
1062
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: "Permisos POS" }),
|
|
1063
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Define el alcance (ubicaciones, canales, regiones) y las capacidades de cada operador del Punto de Venta." })
|
|
1064
|
+
] }) }),
|
|
1065
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.DataTable, { instance: table, children: [
|
|
1066
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Toolbar, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex w-full items-center justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Search, { placeholder: "Buscar operador..." }) }) }),
|
|
1067
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Table, {}),
|
|
1068
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.DataTable.Pagination, {})
|
|
1069
|
+
] })
|
|
1070
|
+
] });
|
|
1071
|
+
};
|
|
1072
|
+
const config = adminSdk.defineRouteConfig({
|
|
1073
|
+
label: "Permisos POS",
|
|
1074
|
+
icon: icons.ShieldCheck
|
|
1075
|
+
});
|
|
1076
|
+
const MultiSelect = ({
|
|
1077
|
+
options,
|
|
1078
|
+
value,
|
|
1079
|
+
onChange,
|
|
1080
|
+
placeholder = "Seleccionar...",
|
|
1081
|
+
searchPlaceholder = "Buscar...",
|
|
1082
|
+
disabled = false
|
|
1083
|
+
}) => {
|
|
1084
|
+
const [open, setOpen] = react.useState(false);
|
|
1085
|
+
const [search, setSearch] = react.useState("");
|
|
1086
|
+
const filtered = react.useMemo(() => {
|
|
1087
|
+
const q = search.trim().toLowerCase();
|
|
1088
|
+
if (!q) {
|
|
1089
|
+
return options;
|
|
1090
|
+
}
|
|
1091
|
+
return options.filter(
|
|
1092
|
+
(o) => o.label.toLowerCase().includes(q) || o.value.toLowerCase().includes(q)
|
|
1093
|
+
);
|
|
1094
|
+
}, [options, search]);
|
|
1095
|
+
const selectedOptions = react.useMemo(
|
|
1096
|
+
() => value.map(
|
|
1097
|
+
(v) => options.find((o) => o.value === v) || { value: v, label: v }
|
|
1098
|
+
),
|
|
1099
|
+
[value, options]
|
|
1100
|
+
);
|
|
1101
|
+
const toggle = (val) => {
|
|
1102
|
+
if (value.includes(val)) {
|
|
1103
|
+
onChange(value.filter((v) => v !== val));
|
|
1104
|
+
} else {
|
|
1105
|
+
onChange([...value, val]);
|
|
1106
|
+
}
|
|
1107
|
+
};
|
|
1108
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(ui.Popover, { open, onOpenChange: setOpen, children: [
|
|
1109
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Popover.Trigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1110
|
+
"button",
|
|
1111
|
+
{
|
|
1112
|
+
type: "button",
|
|
1113
|
+
disabled,
|
|
1114
|
+
className: ui.clx(
|
|
1115
|
+
"flex min-h-8 w-full items-center justify-between gap-2 rounded-md border bg-ui-bg-field px-3 py-1.5 text-left shadow-borders-base transition-colors",
|
|
1116
|
+
"hover:bg-ui-bg-field-hover focus:outline-none focus:shadow-borders-interactive-with-active",
|
|
1117
|
+
disabled && "cursor-not-allowed opacity-60"
|
|
1118
|
+
),
|
|
1119
|
+
children: [
|
|
1120
|
+
selectedOptions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-muted", children: placeholder }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1", children: selectedOptions.map((o) => /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", className: "gap-x-1", children: [
|
|
1121
|
+
o.label,
|
|
1122
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1123
|
+
"span",
|
|
1124
|
+
{
|
|
1125
|
+
role: "button",
|
|
1126
|
+
tabIndex: -1,
|
|
1127
|
+
onClick: (e) => {
|
|
1128
|
+
e.stopPropagation();
|
|
1129
|
+
toggle(o.value);
|
|
1130
|
+
},
|
|
1131
|
+
className: "hover:text-ui-fg-base",
|
|
1132
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(icons.XMark, { className: "h-3 w-3" })
|
|
1133
|
+
}
|
|
1134
|
+
)
|
|
1135
|
+
] }, o.value)) }),
|
|
1136
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.ChevronUpDown, { className: "shrink-0 text-ui-fg-muted" })
|
|
1137
|
+
]
|
|
1138
|
+
}
|
|
1139
|
+
) }),
|
|
1140
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1141
|
+
ui.Popover.Content,
|
|
1142
|
+
{
|
|
1143
|
+
align: "start",
|
|
1144
|
+
sideOffset: 4,
|
|
1145
|
+
className: "z-50 w-[var(--radix-popover-trigger-width)] min-w-[280px] p-0",
|
|
1146
|
+
children: [
|
|
1147
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-ui-border-base p-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1148
|
+
ui.Input,
|
|
1149
|
+
{
|
|
1150
|
+
autoFocus: true,
|
|
1151
|
+
placeholder: searchPlaceholder,
|
|
1152
|
+
value: search,
|
|
1153
|
+
onChange: (e) => setSearch(e.target.value)
|
|
1154
|
+
}
|
|
1155
|
+
) }),
|
|
1156
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-h-64 overflow-auto p-1", children: filtered.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1157
|
+
ui.Text,
|
|
1158
|
+
{
|
|
1159
|
+
size: "small",
|
|
1160
|
+
className: "px-2 py-3 text-center text-ui-fg-muted",
|
|
1161
|
+
children: "Sin resultados"
|
|
1162
|
+
}
|
|
1163
|
+
) : filtered.map((o) => {
|
|
1164
|
+
const checked = value.includes(o.value);
|
|
1165
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1166
|
+
"button",
|
|
1167
|
+
{
|
|
1168
|
+
type: "button",
|
|
1169
|
+
onClick: () => toggle(o.value),
|
|
1170
|
+
className: "flex w-full items-center gap-x-2 rounded-md px-2 py-1.5 text-left hover:bg-ui-bg-base-hover",
|
|
1171
|
+
children: [
|
|
1172
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1173
|
+
ui.Checkbox,
|
|
1174
|
+
{
|
|
1175
|
+
checked,
|
|
1176
|
+
className: "pointer-events-none"
|
|
1177
|
+
}
|
|
1178
|
+
),
|
|
1179
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", children: o.label })
|
|
1180
|
+
]
|
|
1181
|
+
},
|
|
1182
|
+
o.value
|
|
1183
|
+
);
|
|
1184
|
+
}) }),
|
|
1185
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between border-t border-ui-border-base px-2 py-1.5", children: [
|
|
1186
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "xsmall", className: "text-ui-fg-subtle", children: [
|
|
1187
|
+
value.length,
|
|
1188
|
+
" seleccionado",
|
|
1189
|
+
value.length === 1 ? "" : "s"
|
|
1190
|
+
] }),
|
|
1191
|
+
value.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1192
|
+
ui.Button,
|
|
1193
|
+
{
|
|
1194
|
+
size: "small",
|
|
1195
|
+
variant: "transparent",
|
|
1196
|
+
onClick: () => onChange([]),
|
|
1197
|
+
children: "Limpiar"
|
|
1198
|
+
}
|
|
1199
|
+
)
|
|
1200
|
+
] })
|
|
1201
|
+
]
|
|
1202
|
+
}
|
|
1203
|
+
)
|
|
1204
|
+
] });
|
|
1205
|
+
};
|
|
1206
|
+
const AllowListField = ({
|
|
1207
|
+
label,
|
|
1208
|
+
description,
|
|
1209
|
+
icon,
|
|
1210
|
+
options,
|
|
1211
|
+
restricted,
|
|
1212
|
+
onRestrictedChange,
|
|
1213
|
+
selected,
|
|
1214
|
+
onSelectedChange,
|
|
1215
|
+
error,
|
|
1216
|
+
searchPlaceholder
|
|
1217
|
+
}) => {
|
|
1218
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3 rounded-lg border border-ui-border-base p-4", children: [
|
|
1219
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-x-4", children: [
|
|
1220
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-x-2", children: [
|
|
1221
|
+
icon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-0.5 text-ui-fg-subtle", children: icon }) : null,
|
|
1222
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
|
|
1223
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { weight: "plus", children: label }),
|
|
1224
|
+
description ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: description }) : null
|
|
1225
|
+
] })
|
|
1226
|
+
] }),
|
|
1227
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-x-2", children: [
|
|
1228
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: restricted ? "Restringido" : "Todas" }),
|
|
1229
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1230
|
+
ui.Switch,
|
|
1231
|
+
{
|
|
1232
|
+
checked: restricted,
|
|
1233
|
+
onCheckedChange: onRestrictedChange
|
|
1234
|
+
}
|
|
1235
|
+
)
|
|
1236
|
+
] })
|
|
1237
|
+
] }),
|
|
1238
|
+
restricted ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [
|
|
1239
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1240
|
+
MultiSelect,
|
|
1241
|
+
{
|
|
1242
|
+
options,
|
|
1243
|
+
value: selected,
|
|
1244
|
+
onChange: onSelectedChange,
|
|
1245
|
+
searchPlaceholder
|
|
1246
|
+
}
|
|
1247
|
+
),
|
|
1248
|
+
error ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-error", children: error }) : null
|
|
1249
|
+
] }) : null
|
|
1250
|
+
] });
|
|
1251
|
+
};
|
|
1252
|
+
const NONE$1 = "__none__";
|
|
1253
|
+
const CapabilitiesSection = ({
|
|
1254
|
+
role,
|
|
1255
|
+
onRoleChange,
|
|
1256
|
+
capabilities,
|
|
1257
|
+
onCapabilitiesChange
|
|
1258
|
+
}) => {
|
|
1259
|
+
const preset = presetForRole(role);
|
|
1260
|
+
const setCapability = (key, nextValue) => {
|
|
1261
|
+
onCapabilitiesChange({ ...capabilities, [key]: nextValue });
|
|
1262
|
+
};
|
|
1263
|
+
const diffCount = Object.keys(preset).filter(
|
|
1264
|
+
(k) => capabilities[k] !== preset[k]
|
|
1265
|
+
).length;
|
|
1266
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-4", children: [
|
|
1267
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
1268
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { weight: "plus", children: "Rol POS" }),
|
|
1269
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "El rol aplica un preset de capacidades. Puedes ajustar capacidades puntuales abajo; se guarda el mapa completo resuelto." }),
|
|
1270
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
1271
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-xs flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1272
|
+
ui.Select,
|
|
1273
|
+
{
|
|
1274
|
+
value: role || NONE$1,
|
|
1275
|
+
onValueChange: (v) => onRoleChange(v === NONE$1 ? "" : v),
|
|
1276
|
+
children: [
|
|
1277
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Sin rol" }) }),
|
|
1278
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
|
|
1279
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: NONE$1, children: "Sin rol (todo permitido)" }),
|
|
1280
|
+
POS_ROLES.map((r) => /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: r.value, children: r.label }, r.value))
|
|
1281
|
+
] })
|
|
1282
|
+
]
|
|
1283
|
+
}
|
|
1284
|
+
) }),
|
|
1285
|
+
diffCount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1286
|
+
ui.Button,
|
|
1287
|
+
{
|
|
1288
|
+
size: "small",
|
|
1289
|
+
variant: "secondary",
|
|
1290
|
+
onClick: () => onCapabilitiesChange({ ...preset }),
|
|
1291
|
+
children: [
|
|
1292
|
+
/* @__PURE__ */ jsxRuntime.jsx(icons.ArrowUturnLeft, {}),
|
|
1293
|
+
"Restablecer al preset"
|
|
1294
|
+
]
|
|
1295
|
+
}
|
|
1296
|
+
) : null
|
|
1297
|
+
] })
|
|
1298
|
+
] }),
|
|
1299
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
1300
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { weight: "plus", children: "Capacidades" }),
|
|
1301
|
+
diffCount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(ui.Badge, { size: "2xsmall", color: "orange", children: [
|
|
1302
|
+
diffCount,
|
|
1303
|
+
" distinta",
|
|
1304
|
+
diffCount === 1 ? "" : "s",
|
|
1305
|
+
" del preset"
|
|
1306
|
+
] }) : null
|
|
1307
|
+
] }),
|
|
1308
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-y-5", children: CAPABILITY_GROUPS.map((group) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
1309
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1310
|
+
ui.Text,
|
|
1311
|
+
{
|
|
1312
|
+
size: "small",
|
|
1313
|
+
weight: "plus",
|
|
1314
|
+
className: "text-ui-fg-subtle",
|
|
1315
|
+
children: group.title
|
|
1316
|
+
}
|
|
1317
|
+
),
|
|
1318
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col divide-y divide-ui-border-base rounded-lg border border-ui-border-base", children: group.items.map((item) => {
|
|
1319
|
+
const value = capabilities[item.key] ?? true;
|
|
1320
|
+
const differs = value !== (preset[item.key] ?? true);
|
|
1321
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1322
|
+
"div",
|
|
1323
|
+
{
|
|
1324
|
+
className: "flex items-center justify-between gap-x-4 px-4 py-3",
|
|
1325
|
+
children: [
|
|
1326
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
|
|
1327
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-2", children: [
|
|
1328
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", weight: "plus", children: item.label }),
|
|
1329
|
+
differs ? /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { size: "2xsmall", color: "orange", children: "≠ preset" }) : null
|
|
1330
|
+
] }),
|
|
1331
|
+
item.description ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1332
|
+
ui.Text,
|
|
1333
|
+
{
|
|
1334
|
+
size: "small",
|
|
1335
|
+
className: "text-ui-fg-subtle",
|
|
1336
|
+
children: item.description
|
|
1337
|
+
}
|
|
1338
|
+
) : null
|
|
1339
|
+
] }),
|
|
1340
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1341
|
+
ui.Switch,
|
|
1342
|
+
{
|
|
1343
|
+
checked: value,
|
|
1344
|
+
onCheckedChange: (checked) => setCapability(item.key, checked)
|
|
1345
|
+
}
|
|
1346
|
+
)
|
|
1347
|
+
]
|
|
1348
|
+
},
|
|
1349
|
+
item.key
|
|
1350
|
+
);
|
|
1351
|
+
}) })
|
|
1352
|
+
] }, group.title)) })
|
|
1353
|
+
] });
|
|
1354
|
+
};
|
|
1355
|
+
const NONE = "__none__";
|
|
1356
|
+
const buildInitialState = (metadata) => {
|
|
1357
|
+
const role = metadata.pos_role ?? "";
|
|
1358
|
+
return {
|
|
1359
|
+
stockRestricted: Array.isArray(metadata.pos_stock_location_ids),
|
|
1360
|
+
stockIds: metadata.pos_stock_location_ids ?? [],
|
|
1361
|
+
channelRestricted: Array.isArray(metadata.pos_sales_channel_ids),
|
|
1362
|
+
channelIds: metadata.pos_sales_channel_ids ?? [],
|
|
1363
|
+
regionRestricted: Array.isArray(metadata.pos_region_ids),
|
|
1364
|
+
regionIds: metadata.pos_region_ids ?? [],
|
|
1365
|
+
shippingRestricted: Array.isArray(metadata.pos_shipping_option_ids),
|
|
1366
|
+
shippingIds: metadata.pos_shipping_option_ids ?? [],
|
|
1367
|
+
defaultRegionId: metadata.pos_default_region_id ?? "",
|
|
1368
|
+
defaultChannelId: metadata.pos_default_sales_channel_id ?? "",
|
|
1369
|
+
defaultStockId: metadata.pos_default_stock_location_id ?? "",
|
|
1370
|
+
role,
|
|
1371
|
+
// Full resolved map (folds any stored partial caps onto the role preset).
|
|
1372
|
+
capabilities: resolveCapabilities(role, metadata.pos_capabilities)
|
|
1373
|
+
};
|
|
1374
|
+
};
|
|
1375
|
+
const toOptions = (items, decorate) => (items ?? []).map((i) => ({
|
|
1376
|
+
value: i.id,
|
|
1377
|
+
label: decorate ? decorate(i) : i.name || i.id
|
|
1378
|
+
}));
|
|
1379
|
+
const DefaultSelect = ({
|
|
1380
|
+
label,
|
|
1381
|
+
value,
|
|
1382
|
+
onChange,
|
|
1383
|
+
options: opts,
|
|
1384
|
+
disabledHint
|
|
1385
|
+
}) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-2", children: [
|
|
1386
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Label, { weight: "plus", children: label }),
|
|
1387
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
1388
|
+
ui.Select,
|
|
1389
|
+
{
|
|
1390
|
+
value: value || NONE,
|
|
1391
|
+
onValueChange: (v) => onChange(v === NONE ? "" : v),
|
|
1392
|
+
children: [
|
|
1393
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Value, { placeholder: "Sin valor por defecto" }) }),
|
|
1394
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Select.Content, { children: [
|
|
1395
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: NONE, children: "Sin valor por defecto" }),
|
|
1396
|
+
opts.map((o) => /* @__PURE__ */ jsxRuntime.jsx(ui.Select.Item, { value: o.value, children: o.label }, o.value))
|
|
1397
|
+
] })
|
|
1398
|
+
]
|
|
1399
|
+
}
|
|
1400
|
+
),
|
|
1401
|
+
opts.length === 0 && disabledHint ? /* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: disabledHint }) : null
|
|
1402
|
+
] });
|
|
1403
|
+
const PosPermissionEditorPage = () => {
|
|
1404
|
+
const { id } = reactRouterDom.useParams();
|
|
1405
|
+
const navigate = reactRouterDom.useNavigate();
|
|
1406
|
+
const queryClient = reactQuery.useQueryClient();
|
|
1407
|
+
const { data: operatorData, isLoading: operatorLoading } = reactQuery.useQuery({
|
|
1408
|
+
queryFn: () => sdk.client.fetch(`/admin/pos-permissions/${id}`),
|
|
1409
|
+
queryKey: ["pos-permissions-operator", id],
|
|
1410
|
+
enabled: !!id
|
|
1411
|
+
});
|
|
1412
|
+
const { data: options, isLoading: optionsLoading } = reactQuery.useQuery({
|
|
1413
|
+
queryFn: () => sdk.client.fetch(`/admin/pos-permissions/options`),
|
|
1414
|
+
queryKey: ["pos-permissions-options"]
|
|
1415
|
+
});
|
|
1416
|
+
const [form, setForm] = react.useState(null);
|
|
1417
|
+
const initializedFor = react.useRef(null);
|
|
1418
|
+
react.useEffect(() => {
|
|
1419
|
+
const operator2 = operatorData == null ? void 0 : operatorData.operator;
|
|
1420
|
+
if (operator2 && initializedFor.current !== operator2.id) {
|
|
1421
|
+
initializedFor.current = operator2.id;
|
|
1422
|
+
setForm(buildInitialState(operator2.metadata ?? {}));
|
|
1423
|
+
}
|
|
1424
|
+
}, [operatorData]);
|
|
1425
|
+
const stockOptions = react.useMemo(
|
|
1426
|
+
() => toOptions(options == null ? void 0 : options.stock_locations),
|
|
1427
|
+
[options]
|
|
1428
|
+
);
|
|
1429
|
+
const channelOptions = react.useMemo(
|
|
1430
|
+
() => toOptions(options == null ? void 0 : options.sales_channels),
|
|
1431
|
+
[options]
|
|
1432
|
+
);
|
|
1433
|
+
const regionOptions = react.useMemo(
|
|
1434
|
+
() => toOptions(
|
|
1435
|
+
options == null ? void 0 : options.regions,
|
|
1436
|
+
(r) => r.currency_code ? `${r.name} (${String(r.currency_code).toUpperCase()})` : r.name
|
|
1437
|
+
),
|
|
1438
|
+
[options]
|
|
1439
|
+
);
|
|
1440
|
+
const shippingOptions = react.useMemo(
|
|
1441
|
+
() => toOptions(options == null ? void 0 : options.shipping_options),
|
|
1442
|
+
[options]
|
|
1443
|
+
);
|
|
1444
|
+
const saveMutation = reactQuery.useMutation({
|
|
1445
|
+
mutationFn: (payload) => sdk.client.fetch(`/admin/pos-permissions/${id}`, {
|
|
1446
|
+
method: "POST",
|
|
1447
|
+
body: payload
|
|
1448
|
+
}),
|
|
1449
|
+
onSuccess: () => {
|
|
1450
|
+
queryClient.invalidateQueries({ queryKey: ["operators"] });
|
|
1451
|
+
queryClient.invalidateQueries({
|
|
1452
|
+
queryKey: ["pos-permissions-operator", id]
|
|
1453
|
+
});
|
|
1454
|
+
ui.toast.success("Permisos del operador actualizados");
|
|
1455
|
+
},
|
|
1456
|
+
onError: (error) => {
|
|
1457
|
+
ui.toast.error(error.message || "No se pudieron guardar los permisos");
|
|
1458
|
+
}
|
|
1459
|
+
});
|
|
1460
|
+
if (operatorLoading || optionsLoading || !form) {
|
|
1461
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ui.Container, { className: "flex items-center justify-center py-16", children: /* @__PURE__ */ jsxRuntime.jsx(icons.Spinner, { className: "animate-spin text-ui-fg-subtle" }) });
|
|
1462
|
+
}
|
|
1463
|
+
const operator = operatorData == null ? void 0 : operatorData.operator;
|
|
1464
|
+
const operatorName = `${(operator == null ? void 0 : operator.first_name) ?? ""} ${(operator == null ? void 0 : operator.last_name) ?? ""}`.trim() || (operator == null ? void 0 : operator.email) || id;
|
|
1465
|
+
const patch = (changes) => setForm((prev) => prev ? { ...prev, ...changes } : prev);
|
|
1466
|
+
const setStockIds = (ids) => setForm((prev) => {
|
|
1467
|
+
if (!prev) return prev;
|
|
1468
|
+
const next = { ...prev, stockIds: ids };
|
|
1469
|
+
if (prev.defaultStockId && !ids.includes(prev.defaultStockId)) {
|
|
1470
|
+
next.defaultStockId = "";
|
|
1471
|
+
}
|
|
1472
|
+
return next;
|
|
1473
|
+
});
|
|
1474
|
+
const setStockRestricted = (restricted) => setForm((prev) => {
|
|
1475
|
+
if (!prev) return prev;
|
|
1476
|
+
const next = { ...prev, stockRestricted: restricted };
|
|
1477
|
+
if (restricted && prev.defaultStockId && !prev.stockIds.includes(prev.defaultStockId)) {
|
|
1478
|
+
next.defaultStockId = "";
|
|
1479
|
+
}
|
|
1480
|
+
return next;
|
|
1481
|
+
});
|
|
1482
|
+
const setChannelIds = (ids) => setForm((prev) => {
|
|
1483
|
+
if (!prev) return prev;
|
|
1484
|
+
const next = { ...prev, channelIds: ids };
|
|
1485
|
+
if (prev.defaultChannelId && !ids.includes(prev.defaultChannelId)) {
|
|
1486
|
+
next.defaultChannelId = "";
|
|
1487
|
+
}
|
|
1488
|
+
return next;
|
|
1489
|
+
});
|
|
1490
|
+
const setChannelRestricted = (restricted) => setForm((prev) => {
|
|
1491
|
+
if (!prev) return prev;
|
|
1492
|
+
const next = { ...prev, channelRestricted: restricted };
|
|
1493
|
+
if (restricted && prev.defaultChannelId && !prev.channelIds.includes(prev.defaultChannelId)) {
|
|
1494
|
+
next.defaultChannelId = "";
|
|
1495
|
+
}
|
|
1496
|
+
return next;
|
|
1497
|
+
});
|
|
1498
|
+
const setRegionIds = (ids) => setForm((prev) => {
|
|
1499
|
+
if (!prev) return prev;
|
|
1500
|
+
const next = { ...prev, regionIds: ids };
|
|
1501
|
+
if (prev.defaultRegionId && !ids.includes(prev.defaultRegionId)) {
|
|
1502
|
+
next.defaultRegionId = "";
|
|
1503
|
+
}
|
|
1504
|
+
return next;
|
|
1505
|
+
});
|
|
1506
|
+
const setRegionRestricted = (restricted) => setForm((prev) => {
|
|
1507
|
+
if (!prev) return prev;
|
|
1508
|
+
const next = { ...prev, regionRestricted: restricted };
|
|
1509
|
+
if (restricted && prev.defaultRegionId && !prev.regionIds.includes(prev.defaultRegionId)) {
|
|
1510
|
+
next.defaultRegionId = "";
|
|
1511
|
+
}
|
|
1512
|
+
return next;
|
|
1513
|
+
});
|
|
1514
|
+
const regionDefaultOptions = form.regionRestricted ? regionOptions.filter((o) => form.regionIds.includes(o.value)) : regionOptions;
|
|
1515
|
+
const channelDefaultOptions = form.channelRestricted ? channelOptions.filter((o) => form.channelIds.includes(o.value)) : channelOptions;
|
|
1516
|
+
const stockDefaultOptions = form.stockRestricted ? stockOptions.filter((o) => form.stockIds.includes(o.value)) : stockOptions;
|
|
1517
|
+
const handleSave = () => {
|
|
1518
|
+
if (form.stockRestricted && form.stockIds.length === 0) {
|
|
1519
|
+
ui.toast.error(
|
|
1520
|
+
"Selecciona al menos una ubicación o desactiva la restricción de ubicaciones."
|
|
1521
|
+
);
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
const payload = {
|
|
1525
|
+
pos_stock_location_ids: form.stockRestricted ? form.stockIds : null,
|
|
1526
|
+
pos_sales_channel_ids: form.channelRestricted ? form.channelIds : null,
|
|
1527
|
+
pos_region_ids: form.regionRestricted ? form.regionIds : null,
|
|
1528
|
+
pos_shipping_option_ids: form.shippingRestricted ? form.shippingIds : null,
|
|
1529
|
+
pos_default_region_id: form.defaultRegionId || null,
|
|
1530
|
+
pos_default_sales_channel_id: form.defaultChannelId || null,
|
|
1531
|
+
pos_default_stock_location_id: form.defaultStockId || null,
|
|
1532
|
+
pos_role: form.role || null,
|
|
1533
|
+
// Store the full resolved capability map so the frontend reads each
|
|
1534
|
+
// flag directly (cap ausente ⇒ permitido) without a preset table.
|
|
1535
|
+
pos_capabilities: form.capabilities
|
|
1536
|
+
};
|
|
1537
|
+
saveMutation.mutate(payload);
|
|
1538
|
+
};
|
|
1539
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-3", children: [
|
|
1540
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "flex items-center justify-between p-4", children: [
|
|
1541
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-x-3", children: [
|
|
1542
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1543
|
+
ui.IconButton,
|
|
1544
|
+
{
|
|
1545
|
+
size: "small",
|
|
1546
|
+
variant: "transparent",
|
|
1547
|
+
onClick: () => navigate("/pos-permissions"),
|
|
1548
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {})
|
|
1549
|
+
}
|
|
1550
|
+
),
|
|
1551
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
|
|
1552
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h2", children: operatorName }),
|
|
1553
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: [
|
|
1554
|
+
operator == null ? void 0 : operator.email,
|
|
1555
|
+
" · Permisos del Punto de Venta"
|
|
1556
|
+
] })
|
|
1557
|
+
] })
|
|
1558
|
+
] }),
|
|
1559
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1560
|
+
ui.Button,
|
|
1561
|
+
{
|
|
1562
|
+
size: "small",
|
|
1563
|
+
onClick: handleSave,
|
|
1564
|
+
isLoading: saveMutation.isPending,
|
|
1565
|
+
children: "Guardar cambios"
|
|
1566
|
+
}
|
|
1567
|
+
)
|
|
1568
|
+
] }),
|
|
1569
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "flex flex-col gap-y-4 p-6", children: [
|
|
1570
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [
|
|
1571
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Alcance de catálogo" }),
|
|
1572
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: 'Restringe a qué ubicaciones, canales, regiones y envíos puede acceder. "Todas" significa sin restricción.' })
|
|
1573
|
+
] }),
|
|
1574
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1575
|
+
AllowListField,
|
|
1576
|
+
{
|
|
1577
|
+
label: "Ubicaciones (sucursales pickup)",
|
|
1578
|
+
description: "Ubicaciones de surtido/recolección que puede usar.",
|
|
1579
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(icons.Buildings, {}),
|
|
1580
|
+
options: stockOptions,
|
|
1581
|
+
restricted: form.stockRestricted,
|
|
1582
|
+
onRestrictedChange: setStockRestricted,
|
|
1583
|
+
selected: form.stockIds,
|
|
1584
|
+
onSelectedChange: setStockIds,
|
|
1585
|
+
searchPlaceholder: "Buscar ubicación...",
|
|
1586
|
+
error: form.stockRestricted && form.stockIds.length === 0 ? "Selecciona al menos una ubicación." : void 0
|
|
1587
|
+
}
|
|
1588
|
+
),
|
|
1589
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1590
|
+
AllowListField,
|
|
1591
|
+
{
|
|
1592
|
+
label: "Canales de venta",
|
|
1593
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(icons.ChannelsSolid, {}),
|
|
1594
|
+
options: channelOptions,
|
|
1595
|
+
restricted: form.channelRestricted,
|
|
1596
|
+
onRestrictedChange: setChannelRestricted,
|
|
1597
|
+
selected: form.channelIds,
|
|
1598
|
+
onSelectedChange: setChannelIds,
|
|
1599
|
+
searchPlaceholder: "Buscar canal..."
|
|
1600
|
+
}
|
|
1601
|
+
),
|
|
1602
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1603
|
+
AllowListField,
|
|
1604
|
+
{
|
|
1605
|
+
label: "Regiones",
|
|
1606
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(icons.CurrencyDollar, {}),
|
|
1607
|
+
options: regionOptions,
|
|
1608
|
+
restricted: form.regionRestricted,
|
|
1609
|
+
onRestrictedChange: setRegionRestricted,
|
|
1610
|
+
selected: form.regionIds,
|
|
1611
|
+
onSelectedChange: setRegionIds,
|
|
1612
|
+
searchPlaceholder: "Buscar región..."
|
|
1613
|
+
}
|
|
1614
|
+
),
|
|
1615
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1616
|
+
AllowListField,
|
|
1617
|
+
{
|
|
1618
|
+
label: "Opciones de envío",
|
|
1619
|
+
icon: /* @__PURE__ */ jsxRuntime.jsx(icons.Tag, {}),
|
|
1620
|
+
options: shippingOptions,
|
|
1621
|
+
restricted: form.shippingRestricted,
|
|
1622
|
+
onRestrictedChange: (r) => patch({ shippingRestricted: r }),
|
|
1623
|
+
selected: form.shippingIds,
|
|
1624
|
+
onSelectedChange: (ids) => patch({ shippingIds: ids }),
|
|
1625
|
+
searchPlaceholder: "Buscar opción de envío..."
|
|
1626
|
+
}
|
|
1627
|
+
)
|
|
1628
|
+
] }),
|
|
1629
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "flex flex-col gap-y-4 p-6", children: [
|
|
1630
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [
|
|
1631
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Valores por defecto" }),
|
|
1632
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "Pre-selección al abrir la sesión. Deben pertenecer a su allow-list cuando esté restringida." })
|
|
1633
|
+
] }),
|
|
1634
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-3", children: [
|
|
1635
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1636
|
+
DefaultSelect,
|
|
1637
|
+
{
|
|
1638
|
+
label: "Región por defecto",
|
|
1639
|
+
value: form.defaultRegionId,
|
|
1640
|
+
onChange: (v) => patch({ defaultRegionId: v }),
|
|
1641
|
+
options: regionDefaultOptions,
|
|
1642
|
+
disabledHint: "Restringe regiones y selecciona alguna para elegir un default."
|
|
1643
|
+
}
|
|
1644
|
+
),
|
|
1645
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1646
|
+
DefaultSelect,
|
|
1647
|
+
{
|
|
1648
|
+
label: "Canal por defecto",
|
|
1649
|
+
value: form.defaultChannelId,
|
|
1650
|
+
onChange: (v) => patch({ defaultChannelId: v }),
|
|
1651
|
+
options: channelDefaultOptions,
|
|
1652
|
+
disabledHint: "Restringe canales y selecciona alguno para elegir un default."
|
|
1653
|
+
}
|
|
1654
|
+
),
|
|
1655
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1656
|
+
DefaultSelect,
|
|
1657
|
+
{
|
|
1658
|
+
label: "Ubicación por defecto",
|
|
1659
|
+
value: form.defaultStockId,
|
|
1660
|
+
onChange: (v) => patch({ defaultStockId: v }),
|
|
1661
|
+
options: stockDefaultOptions,
|
|
1662
|
+
disabledHint: "Restringe ubicaciones y selecciona alguna para elegir un default."
|
|
1663
|
+
}
|
|
1664
|
+
)
|
|
1665
|
+
] })
|
|
1666
|
+
] }),
|
|
1667
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "flex flex-col gap-y-4 p-6", children: [
|
|
1668
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-y-1", children: [
|
|
1669
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Heading, { level: "h3", children: "Rol y capacidades" }),
|
|
1670
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Text, { size: "small", className: "text-ui-fg-subtle", children: "El rol define un preset; las capacidades permiten ajustes puntuales. El backend debe re-validar las acciones sensibles." })
|
|
1671
|
+
] }),
|
|
1672
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1673
|
+
CapabilitiesSection,
|
|
1674
|
+
{
|
|
1675
|
+
role: form.role,
|
|
1676
|
+
onRoleChange: (role) => patch({ role, capabilities: presetForRole(role) }),
|
|
1677
|
+
capabilities: form.capabilities,
|
|
1678
|
+
onCapabilitiesChange: (capabilities) => patch({ capabilities })
|
|
1679
|
+
}
|
|
1680
|
+
)
|
|
1681
|
+
] }),
|
|
1682
|
+
/* @__PURE__ */ jsxRuntime.jsxs(ui.Container, { className: "flex items-center justify-end gap-x-2 p-4", children: [
|
|
1683
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.Button, { asChild: true, size: "small", variant: "secondary", children: /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { to: "/pos-permissions", children: "Volver" }) }),
|
|
1684
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1685
|
+
ui.Button,
|
|
1686
|
+
{
|
|
1687
|
+
size: "small",
|
|
1688
|
+
onClick: handleSave,
|
|
1689
|
+
isLoading: saveMutation.isPending,
|
|
1690
|
+
children: "Guardar cambios"
|
|
1691
|
+
}
|
|
1692
|
+
)
|
|
1693
|
+
] })
|
|
1694
|
+
] });
|
|
1695
|
+
};
|
|
393
1696
|
const i18nTranslations0 = {};
|
|
394
1697
|
const widgetModule = { widgets: [] };
|
|
395
1698
|
const routeModule = {
|
|
@@ -397,15 +1700,43 @@ const routeModule = {
|
|
|
397
1700
|
{
|
|
398
1701
|
Component: OperatorsPage,
|
|
399
1702
|
path: "/operators"
|
|
1703
|
+
},
|
|
1704
|
+
{
|
|
1705
|
+
Component: PaymentOptionsPage,
|
|
1706
|
+
path: "/payment-options"
|
|
1707
|
+
},
|
|
1708
|
+
{
|
|
1709
|
+
Component: PosPermissionsPage,
|
|
1710
|
+
path: "/pos-permissions"
|
|
1711
|
+
},
|
|
1712
|
+
{
|
|
1713
|
+
Component: PosPermissionEditorPage,
|
|
1714
|
+
path: "/pos-permissions/:id"
|
|
400
1715
|
}
|
|
401
1716
|
]
|
|
402
1717
|
};
|
|
403
1718
|
const menuItemModule = {
|
|
404
1719
|
menuItems: [
|
|
1720
|
+
{
|
|
1721
|
+
label: config$1.label,
|
|
1722
|
+
icon: config$1.icon,
|
|
1723
|
+
path: "/payment-options",
|
|
1724
|
+
nested: void 0,
|
|
1725
|
+
rank: void 0,
|
|
1726
|
+
translationNs: void 0
|
|
1727
|
+
},
|
|
1728
|
+
{
|
|
1729
|
+
label: config$2.label,
|
|
1730
|
+
icon: config$2.icon,
|
|
1731
|
+
path: "/operators",
|
|
1732
|
+
nested: void 0,
|
|
1733
|
+
rank: void 0,
|
|
1734
|
+
translationNs: void 0
|
|
1735
|
+
},
|
|
405
1736
|
{
|
|
406
1737
|
label: config.label,
|
|
407
1738
|
icon: config.icon,
|
|
408
|
-
path: "/
|
|
1739
|
+
path: "/pos-permissions",
|
|
409
1740
|
nested: void 0,
|
|
410
1741
|
rank: void 0,
|
|
411
1742
|
translationNs: void 0
|