@fayz-ai/admin 0.36.0 → 0.36.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"CrudPage.d.ts","sourceRoot":"","sources":["../../src/crud/CrudPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAUhF,OAAO,EAAgB,KAAK,YAAY,EAAkB,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAEjG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAkB/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAG1D,KAAK,WAAW,GAAG,OAAO,GAAG,OAAO,CAAA;AAYpC,UAAU,aAAa,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE;IAC9C,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,CAAA;IACvB,QAAQ,EAAE,MAAM,SAAS,CAAC,CAAC,CAAC,CAAA;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,WAAW,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,UAAU,EAAE,CAAA;IACtB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,YAAY,EAAE,CAAA;IAC5D;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB;qDACiD;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAoID,wBAAgB,QAAQ,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,aAAa,CAAC,CAAC,CAAC,qBAolB5M"}
1
+ {"version":3,"file":"CrudPage.d.ts","sourceRoot":"","sources":["../../src/crud/CrudPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAA;AAUhF,OAAO,EAAgB,KAAK,YAAY,EAAkB,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAEjG,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAmB/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAG1D,KAAK,WAAW,GAAG,OAAO,GAAG,OAAO,CAAA;AAYpC,UAAU,aAAa,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE;IAC9C,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,CAAA;IACvB,QAAQ,EAAE,MAAM,SAAS,CAAC,CAAC,CAAC,CAAA;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,WAAW,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,UAAU,EAAE,CAAA;IACtB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,0EAA0E;IAC1E,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,YAAY,EAAE,CAAA;IAC5D;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB;qDACiD;IACjD,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AA0ID,wBAAgB,QAAQ,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,aAAa,CAAC,CAAC,CAAC,qBA4mB5M"}
@@ -20,5 +20,7 @@ export declare function RelatedSection({ parentId, related, compact }: {
20
20
  related: RelatedList;
21
21
  compact?: boolean;
22
22
  }): React.JSX.Element;
23
+ /** Copies a parent's related rows onto another parent (used by list "Duplicate"). */
24
+ export declare function copyRelatedRows(related: RelatedList, fromParentId: string, toParentId: string): Promise<void>;
23
25
  export {};
24
26
  //# sourceMappingURL=RelatedSection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RelatedSection.d.ts","sourceRoot":"","sources":["../../src/crud/RelatedSection.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,KAAK,EAAuB,WAAW,EAAE,MAAM,eAAe,CAAA;AAoCrE,KAAK,oBAAoB,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAM/D,gFAAgF;AAChF,wBAAgB,kBAAkB,IAAI;IACpC,QAAQ,EAAE;QAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,MAAM,IAAI,CAAA;KAAE,CAAA;IACnE,KAAK,EAAE,oBAAoB,CAAA;CAC5B,CAcA;AAED,wBAAgB,uBAAuB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;IAC3D,KAAK,EAAE;QAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,MAAM,IAAI,CAAA;KAAE,CAAA;IAChE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B,qBAEA;AAiED,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE;IAC7D,oEAAoE;IACpE,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,WAAW,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,qBAyYA"}
1
+ {"version":3,"file":"RelatedSection.d.ts","sourceRoot":"","sources":["../../src/crud/RelatedSection.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,KAAK,EAAuB,WAAW,EAAE,MAAM,eAAe,CAAA;AAoCrE,KAAK,oBAAoB,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAM/D,gFAAgF;AAChF,wBAAgB,kBAAkB,IAAI;IACpC,QAAQ,EAAE;QAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,MAAM,IAAI,CAAA;KAAE,CAAA;IACnE,KAAK,EAAE,oBAAoB,CAAA;CAC5B,CAcA;AAED,wBAAgB,uBAAuB,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;IAC3D,KAAK,EAAE;QAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,MAAM,IAAI,CAAA;KAAE,CAAA;IAChE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B,qBAEA;AAiED,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE;IAC7D,oEAAoE;IACpE,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,WAAW,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,qBAqfA;AAED,qFAAqF;AACrF,wBAAsB,eAAe,CAAC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBnH"}
package/dist/index.js CHANGED
@@ -32,7 +32,7 @@ import { useAuthStore, useAuth, buildAppHandoffUrl } from '@fayz-ai/auth';
32
32
  export { useAuth } from '@fayz-ai/auth';
33
33
  import { create, useStore as useStore$1 } from 'zustand';
34
34
  import * as LucideIcons from 'lucide-react';
35
- import { Pencil, Trash2, Upload, Search, ArrowDown, ArrowUp, ArrowUpDown, Check, Download, ChevronLeft, ChevronRight, AlertTriangle, Building2, Inbox, Package, Sparkles, Plug, DollarSign, Clock, X, WandSparkles, Loader2, ArrowDownToLine, ArrowUpFromLine, CheckCircle2, Crown, ArrowRight, History, SquarePen, Settings2, Mic, User, Plus, Minus, ListChecks, ChevronDown, ArrowLeft, Calendar, ClipboardList, CheckSquare, BookOpen, Lock, PinOff, Pin, Eye, Users, StickyNote, CheckSquare2, Square, RotateCcw, Copy, Globe, LayoutTemplate, Store, ChevronsUpDown, ExternalLink, PenSquare, RefreshCw, XCircle, Camera, Edit, Star, Link2Off, Layers, ShoppingBag, MoreHorizontal, ChevronUp, List, LayoutGrid, CircleDollarSign, Receipt, Settings, Mail, MessageCircle, Cake, HelpCircle, Wrench, ShieldCheck, MessageSquare, Paperclip, Infinity, CheckCheck, PartyPopper, Compass, Play, MapPin, Landmark, LocateFixed, ArrowUpCircle, ArrowUpRight, Pause, CalendarDays, CircleCheckBig, UserX, Ban, CircleAlert, CircleEllipsis, FileText, CircleDashed, Power, ArrowRightLeft, File as File$1, FileImage, FileSpreadsheet, AlertCircle, CreditCard, SlidersHorizontal, Puzzle, Shield, Palette, Blocks, ShieldAlert, UserCog, LogOut, Bell, Database, Radio, Zap, Save, ImagePlus, UserPlus, Contact, UserMinus, Send, Sun, Moon, Monitor, Languages, Info, Link2, KeyRound, Bug, Home, Heart, Briefcase, BarChart3, Tag, Truck, Globe2 } from 'lucide-react';
35
+ import { Pencil, Copy, Trash2, Upload, Search, ArrowDown, ArrowUp, ArrowUpDown, Check, Download, ChevronLeft, ChevronRight, AlertTriangle, Building2, Inbox, Package, Sparkles, Plug, DollarSign, Clock, X, WandSparkles, Loader2, ArrowDownToLine, ArrowUpFromLine, CheckCircle2, Crown, ArrowRight, History, SquarePen, Settings2, Mic, User, Plus, Minus, ListChecks, ChevronDown, ArrowLeft, Calendar, ClipboardList, CheckSquare, BookOpen, Lock, PinOff, Pin, Eye, Users, StickyNote, CheckSquare2, Square, RotateCcw, Globe, LayoutTemplate, Store, ChevronsUpDown, ExternalLink, PenSquare, RefreshCw, XCircle, Camera, Edit, Star, Link2Off, Layers, ShoppingBag, MoreHorizontal, ChevronUp, List, LayoutGrid, CircleDollarSign, Receipt, Settings, Mail, MessageCircle, Cake, HelpCircle, Wrench, ShieldCheck, MessageSquare, Paperclip, Infinity, CheckCheck, PartyPopper, Compass, Play, MapPin, Landmark, LocateFixed, ArrowUpCircle, ArrowUpRight, Pause, CalendarDays, CircleCheckBig, UserX, Ban, CircleAlert, CircleEllipsis, FileText, CircleDashed, Power, ArrowRightLeft, File as File$1, FileImage, FileSpreadsheet, AlertCircle, CreditCard, SlidersHorizontal, Puzzle, Shield, Palette, Blocks, ShieldAlert, UserCog, LogOut, Bell, Database, Radio, Zap, Save, ImagePlus, UserPlus, Contact, UserMinus, Send, Sun, Moon, Monitor, Languages, Info, Link2, KeyRound, Bug, Home, Heart, Briefcase, BarChart3, Tag, Truck, Globe2 } from 'lucide-react';
36
36
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
37
37
  import { createPortal } from 'react-dom';
38
38
  import { profileHasSystemPermission, mergeLimitDeclarations } from '@fayz-ai/core/access';
@@ -23472,21 +23472,77 @@ function RelatedSection({ parentId, related, compact }) {
23472
23472
  }
23473
23473
  }
23474
23474
  const shown = child.fields.filter((f) => f.showInTable !== false);
23475
+ const groupKey = related.duplicable ? child.fields.find((f) => f.type === "relation")?.key : void 0;
23476
+ const [lastEdit, setLastEdit] = React127.useState(null);
23477
+ function siblingsToSync(row, key) {
23478
+ if (!groupKey || key === groupKey || row[groupKey] == null) return [];
23479
+ return rows.filter((r) => r.id !== row.id && r[groupKey] === row[groupKey] && r[key] !== row[key]);
23480
+ }
23481
+ async function applyToSiblings(row, key) {
23482
+ const alvos = siblingsToSync(row, key);
23483
+ if (!alvos.length) return;
23484
+ const value = row[key];
23485
+ const ids = new Set(alvos.map((r) => r.id));
23486
+ const antes = rows;
23487
+ setRows((prev) => prev.map((r) => ids.has(r.id) ? { ...r, [key]: value } : r));
23488
+ setLastEdit(null);
23489
+ if (orfa) return;
23490
+ try {
23491
+ await Promise.all(alvos.map((r) => provider2.update(r.id, { [key]: value })));
23492
+ } catch (e) {
23493
+ setRows(antes);
23494
+ toast.error(errorMessage(e));
23495
+ }
23496
+ }
23475
23497
  async function patch(id, key, value, price) {
23476
23498
  const antes = rows.find((r) => r.id === id);
23477
23499
  if (!antes) return;
23500
+ if (key !== groupKey) setLastEdit({ id, key });
23478
23501
  const mudanca = { [key]: value, ...heranca(key, price, { ...antes, [key]: value }) };
23479
23502
  if (Object.entries(mudanca).every(([k, v]) => antes[k] === v)) return;
23480
23503
  setRows((prev) => prev.map((r) => r.id === id ? { ...r, ...mudanca } : r));
23481
23504
  if (orfa) return;
23482
- try {
23483
- await provider2.update(id, mudanca);
23484
- } catch (e) {
23485
- setRows((prev) => prev.map((r) => r.id === id ? antes : r));
23486
- toast.error(errorMessage(e));
23487
- }
23505
+ queueWrite(id, mudanca, antes);
23506
+ }
23507
+ const escritas = React127.useRef(/* @__PURE__ */ new Map());
23508
+ function queueWrite(id, mudanca, antes) {
23509
+ const atual = escritas.current.get(id);
23510
+ const entry = atual ?? { mudanca: {}, antes, fila: Promise.resolve() };
23511
+ entry.mudanca = { ...entry.mudanca, ...mudanca };
23512
+ if (entry.timer) clearTimeout(entry.timer);
23513
+ entry.timer = setTimeout(() => flushWrite(id), 400);
23514
+ escritas.current.set(id, entry);
23515
+ }
23516
+ function flushWrite(id) {
23517
+ const entry = escritas.current.get(id);
23518
+ if (!entry || !Object.keys(entry.mudanca).length) return;
23519
+ const { mudanca, antes } = entry;
23520
+ entry.mudanca = {};
23521
+ entry.timer = void 0;
23522
+ entry.fila = entry.fila.then(async () => {
23523
+ try {
23524
+ await provider2.update(id, mudanca);
23525
+ } catch (e) {
23526
+ setRows((prev) => prev.map((r) => r.id === id ? antes : r));
23527
+ toast.error(errorMessage(e));
23528
+ }
23529
+ });
23530
+ const fila = entry.fila;
23531
+ void fila.then(() => {
23532
+ const e = escritas.current.get(id);
23533
+ if (e && e.fila === fila && !e.timer) escritas.current.delete(id);
23534
+ });
23488
23535
  }
23536
+ React127.useEffect(() => () => {
23537
+ for (const [id, e] of escritas.current) {
23538
+ if (e.timer) {
23539
+ clearTimeout(e.timer);
23540
+ flushWrite(id);
23541
+ }
23542
+ }
23543
+ }, []);
23489
23544
  const required = child.fields.filter((f) => f.required);
23545
+ const criando = React127.useRef(false);
23490
23546
  async function commitDraft(key, next, price) {
23491
23547
  const completo = { ...next, ...heranca(key, price, next) };
23492
23548
  setDraft(completo);
@@ -23495,17 +23551,36 @@ function RelatedSection({ parentId, related, compact }) {
23495
23551
  );
23496
23552
  const pronto = { ...defaults, ...completo };
23497
23553
  if (required.some((f) => pronto[f.key] == null || pronto[f.key] === "")) return;
23554
+ if (criando.current) return;
23498
23555
  if (orfa) {
23499
23556
  setRows((prev) => [...prev, { id: `draft-${prev.length}-${completo[key]}`, ...pronto }]);
23500
23557
  setDraft(null);
23501
23558
  return;
23502
23559
  }
23560
+ criando.current = true;
23503
23561
  try {
23504
23562
  await provider2.create(pronto);
23505
23563
  setDraft(null);
23506
23564
  await load();
23507
23565
  } catch (e) {
23508
23566
  toast.error(errorMessage(e));
23567
+ } finally {
23568
+ criando.current = false;
23569
+ }
23570
+ }
23571
+ async function duplicate(row) {
23572
+ const copia = Object.fromEntries(
23573
+ child.fields.filter((f) => row[f.key] !== void 0).map((f) => [f.key, row[f.key]])
23574
+ );
23575
+ if (orfa) {
23576
+ setRows((prev) => [...prev, { ...copia, id: `draft-${prev.length}-copy` }]);
23577
+ return;
23578
+ }
23579
+ try {
23580
+ await provider2.create(copia);
23581
+ await load();
23582
+ } catch (e) {
23583
+ toast.error(errorMessage(e));
23509
23584
  }
23510
23585
  }
23511
23586
  function heranca(fieldKey, price, linha) {
@@ -23608,25 +23683,57 @@ function RelatedSection({ parentId, related, compact }) {
23608
23683
  error ? /* @__PURE__ */ jsx("p", { className: "rounded-lg border border-destructive/30 bg-destructive-soft px-3 py-2 text-xs text-destructive-soft-foreground", children: error }) : loading ? /* @__PURE__ */ jsx("div", { className: "h-8 w-40 animate-pulse rounded bg-muted/40" }) : rows.length === 0 && draft === null ? /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: compact ? "py-3" : "py-4", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: related.emptyHint ?? "\u2014" }) }) }) : /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "overflow-x-auto p-0", children: /* @__PURE__ */ jsxs("table", { className: "w-full min-w-[26rem] text-xs", children: [
23609
23684
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b text-left text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: [
23610
23685
  shown.map((f) => /* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium", children: f.label }, f.key)),
23611
- /* @__PURE__ */ jsx("th", { className: "w-10 px-3 py-2" })
23686
+ /* @__PURE__ */ jsx("th", { className: related.duplicable ? "w-16 px-3 py-2" : "w-10 px-3 py-2" })
23612
23687
  ] }) }),
23613
23688
  /* @__PURE__ */ jsxs("tbody", { children: [
23614
23689
  rows.map((row) => /* @__PURE__ */ jsxs("tr", { className: "border-b last:border-0", children: [
23615
- shown.map((f) => /* @__PURE__ */ jsx("td", { className: "px-3 py-1.5 align-middle", children: cell(f, row[f.key], (next, price) => {
23616
- void patch(row.id, f.key, next, price);
23617
- }, row) }, f.key)),
23618
- /* @__PURE__ */ jsx("td", { className: "px-3 py-1.5 text-right", children: /* @__PURE__ */ jsx(
23619
- "button",
23620
- {
23621
- type: "button",
23622
- "aria-label": `${label3}: remover`,
23623
- onClick: () => {
23624
- void remove(row.id);
23625
- },
23626
- className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-destructive",
23627
- children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
23628
- }
23629
- ) })
23690
+ shown.map((f) => /* @__PURE__ */ jsxs("td", { className: "px-3 py-1.5 align-middle", children: [
23691
+ cell(f, row[f.key], (next, price) => {
23692
+ void patch(row.id, f.key, next, price);
23693
+ }, row),
23694
+ lastEdit?.id === row.id && lastEdit?.key === f.key && siblingsToSync(row, f.key).length > 0 && /* @__PURE__ */ jsxs(
23695
+ "button",
23696
+ {
23697
+ type: "button",
23698
+ onClick: () => {
23699
+ void applyToSiblings(row, f.key);
23700
+ },
23701
+ className: "mt-1 text-[11px] font-medium text-primary hover:underline",
23702
+ children: [
23703
+ "Aplicar para todos (",
23704
+ siblingsToSync(row, f.key).length,
23705
+ ")"
23706
+ ]
23707
+ }
23708
+ )
23709
+ ] }, f.key)),
23710
+ /* @__PURE__ */ jsxs("td", { className: "whitespace-nowrap px-3 py-1.5 text-right", children: [
23711
+ related.duplicable && /* @__PURE__ */ jsx(
23712
+ "button",
23713
+ {
23714
+ type: "button",
23715
+ "aria-label": `${label3}: duplicar`,
23716
+ title: "Duplicar",
23717
+ onClick: () => {
23718
+ void duplicate(row);
23719
+ },
23720
+ className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
23721
+ children: /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" })
23722
+ }
23723
+ ),
23724
+ /* @__PURE__ */ jsx(
23725
+ "button",
23726
+ {
23727
+ type: "button",
23728
+ "aria-label": `${label3}: remover`,
23729
+ onClick: () => {
23730
+ void remove(row.id);
23731
+ },
23732
+ className: "rounded-md p-1 text-muted-foreground transition-colors hover:bg-muted hover:text-destructive",
23733
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
23734
+ }
23735
+ )
23736
+ ] })
23630
23737
  ] }, row.id)),
23631
23738
  draft !== null && /* @__PURE__ */ jsxs("tr", { className: "border-b last:border-0 bg-muted/20", children: [
23632
23739
  shown.map((f) => /* @__PURE__ */ jsx("td", { className: "px-3 py-1.5 align-middle", children: cell(
@@ -23698,6 +23805,27 @@ function RelatedSection({ parentId, related, compact }) {
23698
23805
  )
23699
23806
  ] });
23700
23807
  }
23808
+ async function copyRelatedRows(related, fromParentId, toParentId) {
23809
+ const data = related.entity.data;
23810
+ if (!data) return;
23811
+ const fk = columnOf(related.entity, related.foreignKey);
23812
+ const reader = resolveDataProvider(
23813
+ { ...related.entity, data: { ...data, filters: { ...data.filters ?? {}, [fk]: fromParentId } } },
23814
+ [],
23815
+ { cache: false }
23816
+ );
23817
+ const writer = resolveDataProvider(
23818
+ { ...related.entity, data: { ...data, defaults: { ...data.defaults ?? {}, [fk]: toParentId } } },
23819
+ [],
23820
+ { cache: false }
23821
+ );
23822
+ const result = await reader.list({ page: 1, pageSize: 500 });
23823
+ const keys = related.entity.fields.map((f) => f.key).filter((k) => k !== related.foreignKey);
23824
+ for (const row of result?.data ?? []) {
23825
+ const copia = Object.fromEntries(keys.filter((k) => row[k] !== void 0).map((k) => [k, row[k]]));
23826
+ await writer.create({ ...copia, [related.foreignKey]: toParentId });
23827
+ }
23828
+ }
23701
23829
  var PRICING_FIELDS = /* @__PURE__ */ new Set(["price", "cost", "currency", "unit"]);
23702
23830
  var STOCK_FIELDS = /* @__PURE__ */ new Set(["stock", "minStock"]);
23703
23831
  var SKIP_FIELDS2 = /* @__PURE__ */ new Set(["status", "isActive", "tags", "imageUrl", "name", "description", "sku", "category"]);
@@ -26467,6 +26595,13 @@ function useCrudColumns(entityDef, options) {
26467
26595
  onSelect: () => setCrudHash(options.basePath, id, "edit"),
26468
26596
  hidden: options.feature ? !can(options.feature, "edit") : false
26469
26597
  },
26598
+ ...options.onDuplicate ? [{
26599
+ id: "duplicate",
26600
+ label: t("crud.list.duplicate"),
26601
+ icon: Copy,
26602
+ onSelect: () => options.onDuplicate(row.original),
26603
+ hidden: options.feature ? !can(options.feature, "create") : false
26604
+ }] : [],
26470
26605
  ...options.onDelete ? [{
26471
26606
  id: "delete",
26472
26607
  label: t("common.delete"),
@@ -26481,7 +26616,7 @@ function useCrudColumns(entityDef, options) {
26481
26616
  });
26482
26617
  }
26483
26618
  return tanCols;
26484
- }, [cols, displayField, options?.basePath, options?.onDelete, options?.feature, options?.readOnly, t, can]);
26619
+ }, [cols, displayField, options?.basePath, options?.onDelete, options?.onDuplicate, options?.feature, options?.readOnly, t, can]);
26485
26620
  }
26486
26621
  function CrudPage({ entityDef: rawEntityDef, useStore: useStore2, basePath, display, feature, readOnly, navTabs, activeNavTab, stats, embedded, emptyHint, aiCreate }) {
26487
26622
  const store = useStore2();
@@ -26572,7 +26707,33 @@ function CrudPage({ entityDef: rawEntityDef, useStore: useStore2, basePath, disp
26572
26707
  const handleInlineUpdate = useCallback(async (id, field, value) => {
26573
26708
  await store.update(id, { [field]: value });
26574
26709
  }, [store]);
26575
- const tanColumns = useCrudColumns(entityDef, { basePath: normalizedBasePath, onDelete: readOnly ? void 0 : (item) => setDeleteItem(item), onInlineUpdate: handleInlineUpdate, feature, readOnly });
26710
+ const handleDuplicate = useCallback(async (row) => {
26711
+ try {
26712
+ const display2 = entityDef.displayField ?? entityDef.fields[0]?.key;
26713
+ const copia = Object.fromEntries(
26714
+ entityDef.fields.filter((f) => f.showInForm !== false && row[f.key] !== void 0).map((f) => [f.key, row[f.key]])
26715
+ );
26716
+ if (display2 && typeof copia[display2] === "string") copia[display2] = `${copia[display2]} ${t("crud.list.copySuffix")}`;
26717
+ const created = await store.create(copia);
26718
+ for (const related of entityDef.relatedSections ?? []) {
26719
+ await copyRelatedRows(related, row.id, created.id);
26720
+ }
26721
+ toast.success(t("crud.list.duplicated", { entity: entityDef.name }));
26722
+ setCrudHash(normalizedBasePath, created.id, "edit");
26723
+ } catch (err) {
26724
+ toast.error(err instanceof Error ? err.message : String(err));
26725
+ }
26726
+ }, [entityDef, store, t, normalizedBasePath]);
26727
+ const tanColumns = useCrudColumns(entityDef, {
26728
+ basePath: normalizedBasePath,
26729
+ onDelete: readOnly ? void 0 : (item) => setDeleteItem(item),
26730
+ onDuplicate: readOnly || !entityDef.duplicable ? void 0 : (item) => {
26731
+ void handleDuplicate(item);
26732
+ },
26733
+ onInlineUpdate: handleInlineUpdate,
26734
+ feature,
26735
+ readOnly
26736
+ });
26576
26737
  const currentOrgId = useOrganizationStore((s) => s.currentOrg?.id);
26577
26738
  const guardImport = useLimitGuard(entityDef.limitKey ?? "");
26578
26739
  useEffect(() => {
@@ -35854,7 +36015,7 @@ function OrderDetailView(props) {
35854
36015
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: tile.label }),
35855
36016
  tile.icon && /* @__PURE__ */ jsx(tile.icon, { className: "h-4 w-4 shrink-0 text-muted-foreground/70" })
35856
36017
  ] }),
35857
- /* @__PURE__ */ jsx("p", { className: "mt-1 truncate text-lg font-semibold", children: tile.value })
36018
+ /* @__PURE__ */ jsx("div", { className: "mt-1 truncate text-lg font-semibold", children: tile.value })
35858
36019
  ] }, tile.id ?? i)) }),
35859
36020
  banner && /* @__PURE__ */ jsxs("div", { className: banner.tone === "warning" ? "flex items-center gap-3 rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900" : "flex items-center gap-3 rounded-lg border border-border bg-muted/40 px-4 py-3 text-sm", children: [
35860
36021
  /* @__PURE__ */ jsx(AlertTriangle, { className: "h-4 w-4 shrink-0" }),