@oondemand/oon-core-front 0.3.30 → 0.3.32

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.js CHANGED
@@ -1369,6 +1369,111 @@ function ReferenceSelect({
1369
1369
  ] });
1370
1370
  }
1371
1371
 
1372
+ // src/components/internal/inputMasks.ts
1373
+ var digitsOnly = (value) => String(value ?? "").replace(/\D/g, "");
1374
+ var normalizedName = (value) => String(value ?? "").normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
1375
+ var hasLetters = (value) => /[a-zA-Z]/.test(String(value ?? ""));
1376
+ function personType(values) {
1377
+ return normalizedName(values?.tipo ?? values?.tipoPessoa ?? values?.pessoaTipo);
1378
+ }
1379
+ function resolveInputMask(field, values) {
1380
+ if (field.mask) return field.mask;
1381
+ if (field.kind === "currency" || field.kind === "currencyConverted") return "currency";
1382
+ if (field.kind && !["string", "number"].includes(field.kind)) return void 0;
1383
+ const name = normalizedName(field.field);
1384
+ if (!name) return void 0;
1385
+ if (name.includes("cnpj")) return "cnpj";
1386
+ if (name.includes("cpf")) return "cpf";
1387
+ if (/documento|document|inscricaofiscal/.test(name)) {
1388
+ const tipo = personType(values);
1389
+ if (tipo === "est" || tipo.includes("estrangeir") || tipo.includes("foreign")) return void 0;
1390
+ if (tipo === "pf" || tipo.includes("fisica")) return "cpf";
1391
+ if (tipo === "pj" || tipo.includes("juridica")) return "cnpj";
1392
+ return "document";
1393
+ }
1394
+ if (/telefone|celular|whatsapp|fone|phone/.test(name)) return "phone";
1395
+ return void 0;
1396
+ }
1397
+ function formatCpf(value) {
1398
+ const digits = digitsOnly(value).slice(0, 11);
1399
+ return digits.replace(/^(\d{3})(\d)/, "$1.$2").replace(/^(\d{3})\.(\d{3})(\d)/, "$1.$2.$3").replace(/\.(\d{3})(\d)/, ".$1-$2");
1400
+ }
1401
+ function formatCnpj(value) {
1402
+ const digits = digitsOnly(value).slice(0, 14);
1403
+ return digits.replace(/^(\d{2})(\d)/, "$1.$2").replace(/^(\d{2})\.(\d{3})(\d)/, "$1.$2.$3").replace(/\.(\d{3})(\d)/, ".$1/$2").replace(/(\/\d{4})(\d)/, "$1-$2");
1404
+ }
1405
+ function formatPhone(value) {
1406
+ const digits = digitsOnly(value).slice(0, 11);
1407
+ if (!digits) return "";
1408
+ if (digits.length <= 2) return `(${digits}`;
1409
+ const ddd = digits.slice(0, 2);
1410
+ const number = digits.slice(2);
1411
+ const prefixLength = number.length > 8 ? 5 : 4;
1412
+ const prefix = number.slice(0, prefixLength);
1413
+ const suffix = number.slice(prefixLength);
1414
+ return `(${ddd}) ${prefix}${suffix ? `-${suffix}` : ""}`;
1415
+ }
1416
+ function parseCurrencyInitial(value) {
1417
+ if (value == null || value === "") return "";
1418
+ if (typeof value === "number") return Number.isFinite(value) ? String(value) : "";
1419
+ const raw = String(value).trim();
1420
+ if (!raw) return "";
1421
+ if (/^-?\d+(?:\.\d+)?$/.test(raw)) return raw;
1422
+ const localized = raw.replace(/[^\d,.-]/g, "").replace(/\.(?=\d{3}(?:\D|$))/g, "").replace(",", ".");
1423
+ const parsed = Number(localized);
1424
+ return Number.isFinite(parsed) ? String(parsed) : "";
1425
+ }
1426
+ function formatCurrency(value) {
1427
+ const normalized = parseCurrencyInitial(value);
1428
+ if (!normalized) return "";
1429
+ const numeric = Number(normalized);
1430
+ if (!Number.isFinite(numeric)) return "";
1431
+ return new Intl.NumberFormat("pt-BR", {
1432
+ style: "currency",
1433
+ currency: "BRL",
1434
+ minimumFractionDigits: 2,
1435
+ maximumFractionDigits: 2
1436
+ }).format(numeric);
1437
+ }
1438
+ function normalizeMaskedValue(value, mask) {
1439
+ if (!mask) return String(value ?? "");
1440
+ if (mask === "currency") return parseCurrencyInitial(value);
1441
+ if (mask === "document" && hasLetters(value)) return String(value ?? "").slice(0, 30);
1442
+ const limit = mask === "cnpj" ? 14 : mask === "cpf" ? 11 : mask === "phone" ? 11 : 14;
1443
+ return digitsOnly(value).slice(0, limit);
1444
+ }
1445
+ function formatMaskedValue(value, mask) {
1446
+ if (!mask) return String(value ?? "");
1447
+ if (mask === "currency") return formatCurrency(value);
1448
+ if (mask === "phone") return formatPhone(value);
1449
+ if (mask === "cpf") return formatCpf(value);
1450
+ if (mask === "cnpj") return formatCnpj(value);
1451
+ if (hasLetters(value)) return String(value ?? "");
1452
+ const digits = digitsOnly(value);
1453
+ return digits.length > 11 ? formatCnpj(digits) : formatCpf(digits);
1454
+ }
1455
+ function parseMaskedInput(value, mask) {
1456
+ if (!mask) return value;
1457
+ if (mask === "currency") {
1458
+ const digits = digitsOnly(value);
1459
+ if (!digits) return "";
1460
+ const amount = Number(digits) / 100;
1461
+ return `${value.includes("-") ? "-" : ""}${amount.toFixed(2)}`;
1462
+ }
1463
+ return normalizeMaskedValue(value, mask);
1464
+ }
1465
+ function inputModeForMask(mask) {
1466
+ if (mask === "document") return "text";
1467
+ return mask ? "numeric" : void 0;
1468
+ }
1469
+ function maxLengthForMask(mask) {
1470
+ if (mask === "cpf") return 14;
1471
+ if (mask === "cnpj") return 18;
1472
+ if (mask === "document") return 30;
1473
+ if (mask === "phone") return 15;
1474
+ return void 0;
1475
+ }
1476
+
1372
1477
  // src/components/internal/DynamicForm.tsx
1373
1478
  import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
1374
1479
  var idFor = (field) => `oon-form-field-${field.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
@@ -1404,6 +1509,9 @@ function toValue(value, kind) {
1404
1509
  }
1405
1510
  return String(value);
1406
1511
  }
1512
+ function initialFieldValue(field, value) {
1513
+ return normalizeMaskedValue(toValue(value, field.kind), resolveInputMask(field));
1514
+ }
1407
1515
  function coerce(value, kind) {
1408
1516
  if (value === "") return void 0;
1409
1517
  if (["number", "currency", "currencyConverted"].includes(kind ?? "")) return Number(value);
@@ -1509,7 +1617,7 @@ function resolveReference(field, values) {
1509
1617
  return { filters, disabled: missing.length > 0 };
1510
1618
  }
1511
1619
  function DynamicForm({ title, eyebrow = "Cadastro", fields, initialValues, submitting, error, onSubmit, onCancel, onFieldChange }) {
1512
- const [values, setValues] = useState5(() => Object.fromEntries(fields.map((field) => [field.field, toValue(initialValues?.[field.field], field.kind)])));
1620
+ const [values, setValues] = useState5(() => Object.fromEntries(fields.map((field) => [field.field, initialFieldValue(field, initialValues?.[field.field])])));
1513
1621
  const [errors, setErrors] = useState5({});
1514
1622
  const [dismissed, setDismissed] = useState5(false);
1515
1623
  const items = useMemo4(() => buildItems(fields), [fields]);
@@ -1632,7 +1740,7 @@ function DynamicForm({ title, eyebrow = "Cadastro", fields, initialValues, submi
1632
1740
  }
1633
1741
  if (field.kind === "ref" && field.ref) {
1634
1742
  const reference = resolveReference(field, values);
1635
- return wrap(/* @__PURE__ */ jsx9(ReferenceSelect, { field: field.field, inputId: id, errorId, label, refModel: field.ref, value: values[field.field] ?? "", initialValue: initialValues?.[field.field], required: field.required, invalid: !!message, error: message, filters: reference.filters, emptyFields: field.referenceEmptyFields, disabled: reference.disabled, disabledMessage: field.referenceDisabledMessage, onChange: (value) => change(field.field, value) }));
1743
+ return wrap(/* @__PURE__ */ jsx9(ReferenceSelect, { field: field.field, inputId: id, errorId, label, refModel: field.ref, value: values[field.field] ?? "", initialValue: initialValues?.[field.field], required: field.required, invalid: !!message, error: message, filters: reference.filters, emptyFields: field.referenceEmptyFields, disabled: reference.disabled, disabledMessage: field.referenceDisabledMessage, onChange: (value2) => change(field.field, value2) }));
1636
1744
  }
1637
1745
  if (field.kind === "enum" && field.options?.length) return wrap(/* @__PURE__ */ jsxs5(NativeSelect2.Root, { size: "sm", children: [
1638
1746
  /* @__PURE__ */ jsxs5(NativeSelect2.Field, { id, "aria-invalid": !!message, "aria-describedby": errorId, value: values[field.field] ?? "", ...controlProps(!!message), onChange: (event) => change(field.field, event.currentTarget.value), children: [
@@ -1650,8 +1758,27 @@ function DynamicForm({ title, eyebrow = "Cadastro", fields, initialValues, submi
1650
1758
  /* @__PURE__ */ jsx9(NativeSelect2.Indicator, {})
1651
1759
  ] }));
1652
1760
  if (multiline) return wrap(/* @__PURE__ */ jsx9(Textarea, { id, rows: field.rows ?? 4, resize: "vertical", "aria-invalid": !!message, "aria-describedby": errorId, value: values[field.field] ?? "", ...controlProps(!!message), lineHeight: "1.5", onChange: (event) => change(field.field, event.currentTarget.value) }));
1653
- const type = field.kind === "date" ? "date" : ["number", "currency", "currencyConverted"].includes(field.kind ?? "") ? "number" : "text";
1654
- return wrap(/* @__PURE__ */ jsx9(Input3, { id, type, step: type === "number" ? "any" : void 0, autoComplete: "off", "aria-invalid": !!message, "aria-describedby": errorId, value: values[field.field] ?? "", ...controlProps(!!message), onChange: (event) => change(field.field, event.currentTarget.value) }));
1761
+ const mask = resolveInputMask(field);
1762
+ const type = mask ? "text" : field.kind === "date" ? "date" : field.kind === "number" ? "number" : "text";
1763
+ const value = values[field.field] ?? "";
1764
+ return wrap(
1765
+ /* @__PURE__ */ jsx9(
1766
+ Input3,
1767
+ {
1768
+ id,
1769
+ type,
1770
+ step: type === "number" ? "any" : void 0,
1771
+ inputMode: inputModeForMask(mask),
1772
+ maxLength: maxLengthForMask(mask),
1773
+ autoComplete: "off",
1774
+ "aria-invalid": !!message,
1775
+ "aria-describedby": errorId,
1776
+ value: mask ? formatMaskedValue(value, mask) : value,
1777
+ ...controlProps(!!message),
1778
+ onChange: (event) => change(field.field, parseMaskedInput(event.currentTarget.value, mask))
1779
+ }
1780
+ )
1781
+ );
1655
1782
  };
1656
1783
  const invalid = fields.filter((field) => errors[field.field]);
1657
1784
  const generic = dismissed ? null : messageOf(error);
@@ -1775,7 +1902,13 @@ import { Input as Input4, NativeSelect as NativeSelect3, Textarea as Textarea2 }
1775
1902
  import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
1776
1903
  function CoreInlineEditableCell({ column, meta, value, onChange }) {
1777
1904
  const kind = column.editor ?? meta?.kind ?? column.kind ?? "string";
1778
- const common = { size: "xs", fontSize: "11px", value: value == null ? "" : String(value), onChange: (event) => onChange(event.currentTarget.value) };
1905
+ const plainValue = value == null ? "" : String(value);
1906
+ const common = {
1907
+ size: "xs",
1908
+ fontSize: "11px",
1909
+ value: plainValue,
1910
+ onChange: (event) => onChange(event.currentTarget.value)
1911
+ };
1779
1912
  if (kind === "boolean") return /* @__PURE__ */ jsxs7(NativeSelect3.Root, { size: "xs", children: [
1780
1913
  /* @__PURE__ */ jsxs7(NativeSelect3.Field, { ...common, children: [
1781
1914
  /* @__PURE__ */ jsx11("option", { value: "", children: "\u2014" }),
@@ -1792,8 +1925,21 @@ function CoreInlineEditableCell({ column, meta, value, onChange }) {
1792
1925
  /* @__PURE__ */ jsx11(NativeSelect3.Indicator, {})
1793
1926
  ] });
1794
1927
  if (kind === "textarea") return /* @__PURE__ */ jsx11(Textarea2, { ...common, rows: 2 });
1795
- const type = kind === "date" ? "date" : ["number", "currency"].includes(kind) ? "number" : "text";
1796
- return /* @__PURE__ */ jsx11(Input4, { ...common, type, step: type === "number" ? "any" : void 0 });
1928
+ const mask = resolveInputMask({ field: column.field, kind });
1929
+ const type = mask ? "text" : kind === "date" ? "date" : kind === "number" ? "number" : "text";
1930
+ return /* @__PURE__ */ jsx11(
1931
+ Input4,
1932
+ {
1933
+ size: "xs",
1934
+ fontSize: "11px",
1935
+ type,
1936
+ step: type === "number" ? "any" : void 0,
1937
+ inputMode: inputModeForMask(mask),
1938
+ maxLength: maxLengthForMask(mask),
1939
+ value: mask ? formatMaskedValue(plainValue, mask) : plainValue,
1940
+ onChange: (event) => onChange(parseMaskedInput(event.currentTarget.value, mask))
1941
+ }
1942
+ );
1797
1943
  }
1798
1944
 
1799
1945
  // src/components/internal/CoreRowAction.tsx
@@ -1837,12 +1983,23 @@ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
1837
1983
  function messageOf2(error) {
1838
1984
  return error instanceof Error ? error.message : typeof error === "string" ? error : "N\xE3o foi poss\xEDvel executar a a\xE7\xE3o.";
1839
1985
  }
1840
- function CoreRowAction({ action, row, parent, onOpenDetail, onRefresh }) {
1986
+ function unwrap2(value) {
1987
+ if (!value || typeof value !== "object") return {};
1988
+ const record = value;
1989
+ const data = record.data;
1990
+ if (data && typeof data === "object") return data;
1991
+ return record;
1992
+ }
1993
+ function CoreRowAction({ action, row, parent, onOpenDetail, onRefresh, onUpdate }) {
1841
1994
  const { http } = useOonApi();
1842
1995
  const [loading, setLoading] = useState6(false);
1843
1996
  const [error, setError] = useState6(null);
1997
+ const [formOpen, setFormOpen] = useState6(false);
1998
+ const [formInitialValues, setFormInitialValues] = useState6({});
1999
+ const pipelineAction = action;
1844
2000
  if (evaluateCondition(action.hiddenWhen, row)) return null;
1845
2001
  const disabled = evaluateCondition(action.disabledWhen, row) || loading;
2002
+ const confirmAction = () => action.confirm ? window.confirm(action.confirm.description ?? action.confirm.title ?? `Executar ${action.label}?`) : true;
1846
2003
  const run = async () => {
1847
2004
  setError(null);
1848
2005
  if (action.type === "openDetailModal") return onOpenDetail?.(row, action.initialTab);
@@ -1851,9 +2008,38 @@ function CoreRowAction({ action, row, parent, onOpenDetail, onRefresh }) {
1851
2008
  if (href) window.location.assign(interpolateEndpoint(href, row, parent));
1852
2009
  return;
1853
2010
  }
1854
- if (action.type !== "apiAction" || !action.endpoint) return;
1855
- const ok = action.confirm ? window.confirm(action.confirm.description ?? action.confirm.title ?? `Executar ${action.label}?`) : true;
1856
- if (!ok) return;
2011
+ if (pipelineAction.type === "formAction") {
2012
+ if (!confirmAction()) return;
2013
+ setLoading(true);
2014
+ try {
2015
+ let loaded = {};
2016
+ if (pipelineAction.loadEndpoint) {
2017
+ const url = interpolateEndpoint(pipelineAction.loadEndpoint, row, parent);
2018
+ loaded = unwrap2(await http.request({ url, method: "GET" }));
2019
+ }
2020
+ setFormInitialValues({ ...pipelineAction.initialValues ?? {}, ...loaded });
2021
+ setFormOpen(true);
2022
+ } catch (err) {
2023
+ setError(err);
2024
+ } finally {
2025
+ setLoading(false);
2026
+ }
2027
+ return;
2028
+ }
2029
+ if (pipelineAction.type === "setField" || pipelineAction.type === "transition") {
2030
+ if (!pipelineAction.field || !onUpdate || !confirmAction()) return;
2031
+ setLoading(true);
2032
+ try {
2033
+ await onUpdate({ [pipelineAction.field]: pipelineAction.value });
2034
+ onRefresh?.(pipelineAction.refresh);
2035
+ } catch (err) {
2036
+ setError(err);
2037
+ } finally {
2038
+ setLoading(false);
2039
+ }
2040
+ return;
2041
+ }
2042
+ if (action.type !== "apiAction" || !action.endpoint || !confirmAction()) return;
1857
2043
  setLoading(true);
1858
2044
  try {
1859
2045
  const url = interpolateEndpoint(action.endpoint, row, parent);
@@ -1867,13 +2053,50 @@ function CoreRowAction({ action, row, parent, onOpenDetail, onRefresh }) {
1867
2053
  };
1868
2054
  return /* @__PURE__ */ jsxs8("span", { children: [
1869
2055
  /* @__PURE__ */ jsx12(Button5, { type: "button", size: "xs", minH: "28px", px: "9px", borderRadius: "6px", fontSize: "10px", loading, disabled, onClick: run, children: action.label }),
1870
- error ? /* @__PURE__ */ jsx12(Text7, { mt: "3px", color: "#B42318", fontSize: "9px", children: messageOf2(error) }) : null
2056
+ error ? /* @__PURE__ */ jsx12(Text7, { mt: "3px", color: "#B42318", fontSize: "9px", children: messageOf2(error) }) : null,
2057
+ formOpen && pipelineAction.type === "formAction" ? /* @__PURE__ */ jsx12(
2058
+ DynamicForm,
2059
+ {
2060
+ title: pipelineAction.title ?? pipelineAction.label,
2061
+ eyebrow: "A\xE7\xE3o",
2062
+ fields: pipelineAction.fields ?? [],
2063
+ initialValues: formInitialValues,
2064
+ submitting: loading,
2065
+ error,
2066
+ onSubmit: async (values) => {
2067
+ if (!pipelineAction.endpoint) return;
2068
+ setLoading(true);
2069
+ setError(null);
2070
+ try {
2071
+ const url = interpolateEndpoint(pipelineAction.endpoint, row, parent);
2072
+ await http.request({ url, method: pipelineAction.method ?? "POST", data: values });
2073
+ setFormOpen(false);
2074
+ onRefresh?.(pipelineAction.refresh);
2075
+ } catch (err) {
2076
+ setError(err);
2077
+ } finally {
2078
+ setLoading(false);
2079
+ }
2080
+ },
2081
+ onFieldChange: () => setError(null),
2082
+ onCancel: () => {
2083
+ if (!loading) setFormOpen(false);
2084
+ }
2085
+ }
2086
+ ) : null
1871
2087
  ] });
1872
2088
  }
1873
2089
 
1874
2090
  // src/components/internal/CoreRelatedGrid.tsx
1875
2091
  import { Fragment as Fragment2, jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
1876
- function CoreRelatedGrid({ tab, parent, relations, refreshToken, onRefresh }) {
2092
+ function CoreRelatedGrid({
2093
+ tab: rawTab,
2094
+ parent,
2095
+ relations,
2096
+ refreshToken,
2097
+ onRefresh
2098
+ }) {
2099
+ const tab = rawTab;
1877
2100
  const relation = relationFrom(tab.relation, relations);
1878
2101
  const schemaQuery = useModelSchema(relation?.model ?? "");
1879
2102
  const schema = schemaQuery.data;
@@ -1881,60 +2104,143 @@ function CoreRelatedGrid({ tab, parent, relations, refreshToken, onRefresh }) {
1881
2104
  const queryClient = useQueryClient();
1882
2105
  const parentValue = relation ? parent[relation.parentKey ?? "_id"] : void 0;
1883
2106
  const key = ["oon", "related", relation?.model, relation?.foreignKey, parentValue, refreshToken];
1884
- const query = useQuery5({ queryKey: key, enabled: !!relation && parentValue != null, queryFn: () => resource.list({ [relation.foreignKey]: parentValue, pageSize: 1e3 }) });
1885
- const columns = useMemo5(() => (tab.columns?.length ? tab.columns : schema?.fields.slice(0, 8).map((f) => f.name) ?? []).map((column) => normalizeColumn(column, schema)).filter((column) => !column.hidden), [tab.columns, schema]);
2107
+ const query = useQuery5({
2108
+ queryKey: key,
2109
+ enabled: !!relation && parentValue != null,
2110
+ queryFn: () => resource.list({ [relation.foreignKey]: parentValue, pageSize: 1e3 })
2111
+ });
2112
+ const columns = useMemo5(
2113
+ () => (tab.columns?.length ? tab.columns : schema?.fields.slice(0, 8).map((field) => field.name) ?? []).map((column) => normalizeColumn(column, schema)).filter((column) => !column.hidden),
2114
+ [tab.columns, schema]
2115
+ );
1886
2116
  const [drafts, setDrafts] = useState7({});
1887
2117
  const [errors, setErrors] = useState7({});
2118
+ const [creating, setCreating] = useState7(false);
2119
+ const [createError, setCreateError] = useState7(null);
1888
2120
  const editable = tab.type === "relatedGrid" && tab.editable && tab.editMode === "inline";
1889
- const mutation = useMutation({ mutationFn: ({ id, values }) => resource.update(id, values), onSuccess: () => {
1890
- queryClient.invalidateQueries({ queryKey: key });
2121
+ const canCreate = tab.type === "relatedGrid" && tab.create?.enabled !== false && !!tab.create;
2122
+ const canDelete = tab.type === "relatedGrid" && tab.delete?.enabled === true;
2123
+ const hasActions = editable || canDelete || !!tab.rowActions?.length;
2124
+ const invalidate = async () => {
2125
+ await queryClient.invalidateQueries({ queryKey: key });
1891
2126
  onRefresh?.(["self"]);
1892
- } });
2127
+ };
2128
+ const updateMutation = useMutation({
2129
+ mutationFn: ({ id, values }) => resource.update(id, values),
2130
+ onSuccess: invalidate
2131
+ });
2132
+ const createMutation = useMutation({
2133
+ mutationFn: (values) => resource.create({
2134
+ ...tab.create?.initialValues ?? {},
2135
+ ...values,
2136
+ ...relation && parentValue != null ? { [relation.foreignKey]: parentValue } : {}
2137
+ }),
2138
+ onSuccess: async () => {
2139
+ await invalidate();
2140
+ setCreating(false);
2141
+ setCreateError(null);
2142
+ },
2143
+ onError: setCreateError
2144
+ });
2145
+ const deleteMutation = useMutation({
2146
+ mutationFn: (id) => resource.delete(id),
2147
+ onSuccess: invalidate
2148
+ });
1893
2149
  if (!relation) return /* @__PURE__ */ jsx13(Text8, { color: "#B42318", children: "Rela\xE7\xE3o n\xE3o encontrada." });
1894
- if (query.isLoading) return /* @__PURE__ */ jsx13(Text8, { color: "#667085", children: "Carregando..." });
2150
+ if (query.isLoading || schemaQuery.isLoading) return /* @__PURE__ */ jsx13(Text8, { color: "#667085", children: "Carregando..." });
1895
2151
  const rows = query.data?.results ?? [];
1896
- const setDraft = (id, field, value) => setDrafts((current) => ({ ...current, [id]: { ...current[id] ?? {}, [field]: value } }));
1897
- return /* @__PURE__ */ jsx13(Box6, { overflowX: "auto", borderWidth: "1px", borderColor: "#E5E9ED", borderRadius: "10px", children: /* @__PURE__ */ jsxs9(Table2.Root, { size: "sm", minW: "760px", children: [
1898
- /* @__PURE__ */ jsx13(Table2.Header, { children: /* @__PURE__ */ jsxs9(Table2.Row, { bg: "#F4F7F9", children: [
1899
- columns.map((c) => /* @__PURE__ */ jsx13(Table2.ColumnHeader, { fontSize: "10px", children: c.label ?? c.field }, c.field)),
1900
- editable || tab.rowActions?.length ? /* @__PURE__ */ jsx13(Table2.ColumnHeader, { children: "A\xE7\xF5es" }) : null
2152
+ const setDraft = (id, field, value) => setDrafts((current) => ({
2153
+ ...current,
2154
+ [id]: { ...current[id] ?? {}, [field]: value }
2155
+ }));
2156
+ const createFields = tab.create?.fields?.length ? tab.create.fields : formFieldsFromMeta(schema?.fields ?? []).filter((field) => ![relation.foreignKey, "_id", "createdAt", "updatedAt"].includes(field.field));
2157
+ return /* @__PURE__ */ jsxs9(Box6, { children: [
2158
+ canCreate ? /* @__PURE__ */ jsx13(Flex5, { justify: "flex-end", mb: "10px", children: /* @__PURE__ */ jsx13(Button6, { size: "sm", colorPalette: "brand", onClick: () => {
2159
+ setCreateError(null);
2160
+ setCreating(true);
2161
+ }, children: tab.create?.label ?? "Novo registro" }) }) : null,
2162
+ /* @__PURE__ */ jsx13(Box6, { overflowX: "auto", borderWidth: "1px", borderColor: "#E5E9ED", borderRadius: "10px", children: /* @__PURE__ */ jsxs9(Table2.Root, { size: "sm", minW: "760px", children: [
2163
+ /* @__PURE__ */ jsx13(Table2.Header, { children: /* @__PURE__ */ jsxs9(Table2.Row, { bg: "#F4F7F9", children: [
2164
+ columns.map((column) => /* @__PURE__ */ jsx13(Table2.ColumnHeader, { fontSize: "10px", children: column.label ?? column.field }, column.field)),
2165
+ hasActions ? /* @__PURE__ */ jsx13(Table2.ColumnHeader, { children: "A\xE7\xF5es" }) : null
2166
+ ] }) }),
2167
+ /* @__PURE__ */ jsxs9(Table2.Body, { children: [
2168
+ rows.map((row, index) => {
2169
+ const id = String(row._id ?? row.id ?? index);
2170
+ const draft = drafts[id] ?? {};
2171
+ const dirty = Object.keys(draft).length > 0;
2172
+ return /* @__PURE__ */ jsxs9(Table2.Row, { bg: dirty ? "#FFFBEB" : index % 2 ? "#FBFCFD" : "white", children: [
2173
+ columns.map((column) => {
2174
+ const value = column.field in draft ? draft[column.field] : row[column.field];
2175
+ const meta = schema?.fields.find((field) => field.name === column.field);
2176
+ const canEdit = editable && column.readonly !== true && (column.editable === true || !column.readonly);
2177
+ return /* @__PURE__ */ jsx13(Table2.Cell, { fontSize: "11px", children: canEdit ? /* @__PURE__ */ jsx13(CoreInlineEditableCell, { column, meta, value, onChange: (next) => setDraft(id, column.field, next) }) : column.display === "badgeExists" ? /* @__PURE__ */ jsx13(Badge3, { colorPalette: value ? "green" : "gray", children: value ? "Sim" : "N\xE3o" }) : formatCell(value, column.kind) }, column.field);
2178
+ }),
2179
+ hasActions ? /* @__PURE__ */ jsx13(Table2.Cell, { children: /* @__PURE__ */ jsxs9(Flex5, { gap: "5px", align: "center", wrap: "wrap", children: [
2180
+ dirty ? /* @__PURE__ */ jsxs9(Fragment2, { children: [
2181
+ /* @__PURE__ */ jsx13(
2182
+ Button6,
2183
+ {
2184
+ size: "xs",
2185
+ loading: updateMutation.isPending,
2186
+ onClick: () => {
2187
+ setErrors((current) => ({ ...current, [id]: void 0 }));
2188
+ updateMutation.mutate({ id, values: draft }, {
2189
+ onSuccess: () => setDrafts((current) => {
2190
+ const next = { ...current };
2191
+ delete next[id];
2192
+ return next;
2193
+ }),
2194
+ onError: (error) => setErrors((current) => ({ ...current, [id]: error }))
2195
+ });
2196
+ },
2197
+ children: "Salvar"
2198
+ }
2199
+ ),
2200
+ /* @__PURE__ */ jsx13(Button6, { size: "xs", variant: "outline", onClick: () => setDrafts((current) => {
2201
+ const next = { ...current };
2202
+ delete next[id];
2203
+ return next;
2204
+ }), children: "Cancelar" })
2205
+ ] }) : null,
2206
+ tab.rowActions?.map((action) => /* @__PURE__ */ jsx13(CoreRowAction, { action, row, parent, onRefresh }, action.id ?? action.label)),
2207
+ canDelete ? /* @__PURE__ */ jsx13(
2208
+ Button6,
2209
+ {
2210
+ size: "xs",
2211
+ variant: "outline",
2212
+ loading: deleteMutation.isPending,
2213
+ onClick: () => {
2214
+ const message = tab.delete?.confirm ?? "Excluir este registro relacionado?";
2215
+ if (window.confirm(message)) deleteMutation.mutate(id);
2216
+ },
2217
+ children: "Excluir"
2218
+ }
2219
+ ) : null,
2220
+ errors[id] ? /* @__PURE__ */ jsx13(Text8, { color: "#B42318", fontSize: "9px", children: "Erro ao salvar." }) : null
2221
+ ] }) }) : null
2222
+ ] }, id);
2223
+ }),
2224
+ rows.length === 0 ? /* @__PURE__ */ jsx13(Table2.Row, { children: /* @__PURE__ */ jsx13(Table2.Cell, { colSpan: columns.length + (hasActions ? 1 : 0), children: /* @__PURE__ */ jsx13(Text8, { p: "8", textAlign: "center", color: "#98A2B3", children: "Nenhum registro relacionado." }) }) }) : null
2225
+ ] })
1901
2226
  ] }) }),
1902
- /* @__PURE__ */ jsxs9(Table2.Body, { children: [
1903
- rows.map((row, index) => {
1904
- const id = String(row._id ?? row.id ?? index);
1905
- const draft = drafts[id] ?? {};
1906
- const dirty = Object.keys(draft).length > 0;
1907
- return /* @__PURE__ */ jsxs9(Table2.Row, { bg: dirty ? "#FFFBEB" : index % 2 ? "#FBFCFD" : "white", children: [
1908
- columns.map((column) => {
1909
- const value = column.field in draft ? draft[column.field] : row[column.field];
1910
- const meta = schema?.fields.find((f) => f.name === column.field);
1911
- const canEdit = editable && column.readonly !== true && (column.editable === true || !column.readonly);
1912
- return /* @__PURE__ */ jsx13(Table2.Cell, { fontSize: "11px", children: canEdit ? /* @__PURE__ */ jsx13(CoreInlineEditableCell, { column, meta, value, onChange: (next) => setDraft(id, column.field, next) }) : column.display === "badgeExists" ? /* @__PURE__ */ jsx13(Badge3, { colorPalette: value ? "green" : "gray", children: value ? "Sim" : "N\xE3o" }) : formatCell(value, column.kind) }, column.field);
1913
- }),
1914
- editable || tab.rowActions?.length ? /* @__PURE__ */ jsx13(Table2.Cell, { children: /* @__PURE__ */ jsxs9(Flex5, { gap: "5px", align: "center", wrap: "wrap", children: [
1915
- dirty ? /* @__PURE__ */ jsxs9(Fragment2, { children: [
1916
- /* @__PURE__ */ jsx13(Button6, { size: "xs", loading: mutation.isPending, onClick: () => {
1917
- setErrors((e) => ({ ...e, [id]: void 0 }));
1918
- mutation.mutate({ id, values: draft }, { onSuccess: () => setDrafts((d) => {
1919
- const n = { ...d };
1920
- delete n[id];
1921
- return n;
1922
- }), onError: (err) => setErrors((e) => ({ ...e, [id]: err })) });
1923
- }, children: "Salvar" }),
1924
- /* @__PURE__ */ jsx13(Button6, { size: "xs", variant: "outline", onClick: () => setDrafts((d) => {
1925
- const n = { ...d };
1926
- delete n[id];
1927
- return n;
1928
- }), children: "Cancelar" })
1929
- ] }) : null,
1930
- tab.rowActions?.map((action) => /* @__PURE__ */ jsx13(CoreRowAction, { action, row, parent, onRefresh }, action.id ?? action.label)),
1931
- errors[id] ? /* @__PURE__ */ jsx13(Text8, { color: "#B42318", fontSize: "9px", children: "Erro ao salvar." }) : null
1932
- ] }) }) : null
1933
- ] }, id);
1934
- }),
1935
- rows.length === 0 ? /* @__PURE__ */ jsx13(Table2.Row, { children: /* @__PURE__ */ jsx13(Table2.Cell, { colSpan: columns.length + 1, children: /* @__PURE__ */ jsx13(Text8, { p: "8", textAlign: "center", color: "#98A2B3", children: "Nenhum registro relacionado." }) }) }) : null
1936
- ] })
1937
- ] }) });
2227
+ creating ? /* @__PURE__ */ jsx13(
2228
+ DynamicForm,
2229
+ {
2230
+ title: tab.create?.label ?? `Novo ${schema?.singular ?? relation.model}`,
2231
+ eyebrow: "Registro relacionado",
2232
+ fields: createFields,
2233
+ initialValues: tab.create?.initialValues,
2234
+ submitting: createMutation.isPending,
2235
+ error: createError,
2236
+ onSubmit: (values) => createMutation.mutate(values),
2237
+ onFieldChange: () => setCreateError(null),
2238
+ onCancel: () => {
2239
+ if (!createMutation.isPending) setCreating(false);
2240
+ }
2241
+ }
2242
+ ) : null
2243
+ ] });
1938
2244
  }
1939
2245
 
1940
2246
  // src/components/internal/CoreTabbedDetail.tsx
@@ -1960,6 +2266,9 @@ function toValue2(value, kind) {
1960
2266
  }
1961
2267
  return String(value);
1962
2268
  }
2269
+ function initialFieldValue2(field, value) {
2270
+ return normalizeMaskedValue(toValue2(value, field.kind), resolveInputMask(field));
2271
+ }
1963
2272
  function isMultiline2(field) {
1964
2273
  return /observa|descricao|mensagem|detalhe|necessidade/i.test(field.field);
1965
2274
  }
@@ -2113,6 +2422,7 @@ function FieldControl({
2113
2422
  )
2114
2423
  ] });
2115
2424
  }
2425
+ const mask = resolveInputMask(field);
2116
2426
  const control = field.kind === "enum" && field.options?.length ? /* @__PURE__ */ jsxs10(NativeSelect4.Root, { size: "sm", children: [
2117
2427
  /* @__PURE__ */ jsxs10(NativeSelect4.Field, { id: inputId, value, ...invalidProps, onChange: (event) => onChange(event.currentTarget.value), children: [
2118
2428
  /* @__PURE__ */ jsx14("option", { value: "", children: "\u2014 Selecione \u2014" }),
@@ -2131,10 +2441,13 @@ function FieldControl({
2131
2441
  {
2132
2442
  id: inputId,
2133
2443
  size: "sm",
2134
- type: field.kind === "date" ? "date" : ["number", "currency", "currencyConverted"].includes(field.kind ?? "") ? "number" : "text",
2135
- value,
2444
+ type: mask ? "text" : field.kind === "date" ? "date" : field.kind === "number" ? "number" : "text",
2445
+ step: !mask && field.kind === "number" ? "any" : void 0,
2446
+ inputMode: inputModeForMask(mask),
2447
+ maxLength: maxLengthForMask(mask),
2448
+ value: mask ? formatMaskedValue(value, mask) : value,
2136
2449
  ...invalidProps,
2137
- onChange: (event) => onChange(event.currentTarget.value)
2450
+ onChange: (event) => onChange(parseMaskedInput(event.currentTarget.value, mask))
2138
2451
  }
2139
2452
  );
2140
2453
  return /* @__PURE__ */ jsxs10(Box7, { children: [
@@ -2183,7 +2496,7 @@ function InlineParentForm({
2183
2496
  const [error, setError] = useState8(null);
2184
2497
  const [fieldErrors, setFieldErrors] = useState8({});
2185
2498
  useEffect5(() => {
2186
- setValues(Object.fromEntries(fields.map((field) => [field.field, toValue2(parent[field.field], field.kind)])));
2499
+ setValues(Object.fromEntries(fields.map((field) => [field.field, initialFieldValue2(field, parent[field.field])])));
2187
2500
  setError(null);
2188
2501
  setFieldErrors({});
2189
2502
  }, [parent, rawFields]);
@@ -2337,6 +2650,7 @@ function CoreDetailModal({
2337
2650
  initialRecord,
2338
2651
  initialTab,
2339
2652
  relations,
2653
+ actions,
2340
2654
  onClose,
2341
2655
  onSaved
2342
2656
  }) {
@@ -2363,7 +2677,6 @@ function CoreDetailModal({
2363
2677
  const list = targets?.length ? targets : ["self"];
2364
2678
  if (list.includes("parent") || list.includes("all")) {
2365
2679
  if (recordId) queryClient.invalidateQueries({ queryKey: ["oon", "detail", model, recordId] });
2366
- onSaved?.();
2367
2680
  }
2368
2681
  setRefreshTokens((current) => {
2369
2682
  const next = { ...current };
@@ -2383,12 +2696,15 @@ function CoreDetailModal({
2383
2696
  const width = config.size === "full" ? "98vw" : config.size === "md" ? "760px" : config.size === "lg" ? "980px" : "1180px";
2384
2697
  const displayTitle = parent?.[config.titleField ?? ""] ? String(parent[config.titleField ?? ""]) : title;
2385
2698
  return /* @__PURE__ */ jsx15(Flex7, { position: "fixed", inset: 0, zIndex: 1e3, align: "center", justify: "center", p: 4, bg: "rgba(15,23,42,.48)", children: /* @__PURE__ */ jsxs11(Box8, { role: "dialog", "aria-modal": "true", w: `min(${width}, 98vw)`, maxH: "94vh", overflow: "hidden", bg: "white", borderRadius: "14px", boxShadow: "0 24px 70px rgba(15,23,42,.24)", children: [
2386
- /* @__PURE__ */ jsxs11(Flex7, { minH: "72px", px: "20px", align: "center", justify: "space-between", borderBottomWidth: "1px", borderColor: "#E5E9ED", children: [
2699
+ /* @__PURE__ */ jsxs11(Flex7, { minH: "72px", px: "20px", align: "center", justify: "space-between", gap: 4, borderBottomWidth: "1px", borderColor: "#E5E9ED", children: [
2387
2700
  /* @__PURE__ */ jsxs11(Box8, { children: [
2388
2701
  /* @__PURE__ */ jsx15(Text10, { color: "#0474AF", fontSize: "10px", fontWeight: "700", textTransform: "uppercase", children: "Detalhe" }),
2389
2702
  /* @__PURE__ */ jsx15(Heading3, { size: "md", children: recordId ? displayTitle : `Novo ${title}` })
2390
2703
  ] }),
2391
- /* @__PURE__ */ jsx15(Button8, { onClick: onClose, children: "Fechar" })
2704
+ /* @__PURE__ */ jsxs11(Flex7, { align: "center", justify: "flex-end", gap: "6px", wrap: "wrap", children: [
2705
+ recordId ? actions : null,
2706
+ /* @__PURE__ */ jsx15(Button8, { onClick: onClose, children: "Fechar" })
2707
+ ] })
2392
2708
  ] }),
2393
2709
  /* @__PURE__ */ jsx15(Box8, { maxH: "calc(94vh - 73px)", p: "20px", overflowY: "auto", children: schemaQuery.isLoading || parentQuery.isLoading ? /* @__PURE__ */ jsx15(Text10, { children: "Carregando..." }) : /* @__PURE__ */ jsx15(
2394
2710
  CoreTabbedDetail,
@@ -2404,7 +2720,7 @@ function CoreDetailModal({
2404
2720
  onRefresh: refresh,
2405
2721
  onParentSaved: (saved) => {
2406
2722
  setParent(saved);
2407
- onSaved?.();
2723
+ onSaved?.(saved);
2408
2724
  refresh(["parent"]);
2409
2725
  }
2410
2726
  }
@@ -2554,7 +2870,7 @@ function CoreDocument({ model, label, endpoint, columns, form }) {
2554
2870
 
2555
2871
  // src/components/CorePipeline.tsx
2556
2872
  import { useState as useState11 } from "react";
2557
- import { Badge as Badge4, Box as Box10, Button as Button9, Flex as Flex8, Heading as Heading5, Stack as Stack5, Text as Text12 } from "@chakra-ui/react";
2873
+ import { Badge as Badge4, Box as Box10, Button as Button9, Flex as Flex8, Heading as Heading5, Input as Input6, NativeSelect as NativeSelect5, Stack as Stack5, Text as Text12 } from "@chakra-ui/react";
2558
2874
  import { useMutation as useMutation3, useQuery as useQuery7, useQueryClient as useQueryClient4 } from "@tanstack/react-query";
2559
2875
  import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
2560
2876
  function ticketId(row) {
@@ -2571,6 +2887,49 @@ function mergeFormFields2(metadataFields, overrides) {
2571
2887
  });
2572
2888
  return merged;
2573
2889
  }
2890
+ function PipelineFilterControl({
2891
+ filter,
2892
+ value,
2893
+ meta,
2894
+ onChange
2895
+ }) {
2896
+ if (filter.type === "ref" && meta?.ref) {
2897
+ return /* @__PURE__ */ jsx18(Box10, { w: "220px", children: /* @__PURE__ */ jsx18(
2898
+ ReferenceSelect,
2899
+ {
2900
+ field: filter.field,
2901
+ inputId: `pipeline-filter-${filter.field}`,
2902
+ label: filter.label ?? filter.field,
2903
+ refModel: meta.ref,
2904
+ value: String(value ?? ""),
2905
+ initialValue: value,
2906
+ required: false,
2907
+ invalid: false,
2908
+ filters: {},
2909
+ disabled: false,
2910
+ onChange: (next) => onChange(next)
2911
+ }
2912
+ ) });
2913
+ }
2914
+ if (filter.type === "select") {
2915
+ return /* @__PURE__ */ jsxs13(NativeSelect5.Root, { size: "sm", w: "190px", children: [
2916
+ /* @__PURE__ */ jsx18(NativeSelect5.Field, { value: String(value ?? ""), onChange: (event) => onChange(event.currentTarget.value), children: (filter.options ?? []).map((option) => /* @__PURE__ */ jsx18("option", { value: String(option.value ?? ""), children: option.label }, String(option.value ?? ""))) }),
2917
+ /* @__PURE__ */ jsx18(NativeSelect5.Indicator, {})
2918
+ ] });
2919
+ }
2920
+ return /* @__PURE__ */ jsx18(
2921
+ Input6,
2922
+ {
2923
+ w: "190px",
2924
+ h: "35px",
2925
+ fontSize: "11px",
2926
+ type: filter.type === "date" ? "date" : "text",
2927
+ placeholder: filter.placeholder ?? filter.label ?? filter.field,
2928
+ value: String(value ?? ""),
2929
+ onChange: (event) => onChange(event.currentTarget.value)
2930
+ }
2931
+ );
2932
+ }
2574
2933
  function CorePipeline({
2575
2934
  model,
2576
2935
  label,
@@ -2580,116 +2939,196 @@ function CorePipeline({
2580
2939
  titleField,
2581
2940
  form,
2582
2941
  relations,
2583
- ticketModal
2942
+ ticketModal,
2943
+ filters = [],
2944
+ create,
2945
+ viewModes = ["board"],
2946
+ defaultView,
2947
+ list,
2948
+ ticketActions = []
2584
2949
  }) {
2585
2950
  const schemaQuery = useModelSchema(model);
2586
2951
  const schema = schemaQuery.data;
2587
2952
  const basePath = endpoint ?? schema?.basePath ?? `/${model.toLowerCase()}s`;
2588
2953
  const resource = useOonResource(basePath);
2589
2954
  const queryClient = useQueryClient4();
2955
+ const allowedViewModes = viewModes.length ? viewModes : ["board"];
2956
+ const initialView = defaultView && allowedViewModes.includes(defaultView) ? defaultView : allowedViewModes[0] ?? "board";
2957
+ const [viewMode, setViewMode] = useState11(initialView);
2958
+ const [filterValues, setFilterValues] = useState11(
2959
+ () => Object.fromEntries(filters.map((filter) => [filter.field, filter.defaultValue ?? ""]))
2960
+ );
2590
2961
  const [selectedTicket, setSelectedTicket] = useState11(null);
2962
+ const [creating, setCreating] = useState11(false);
2591
2963
  const [formError, setFormError] = useState11(null);
2964
+ const effectiveFilters = Object.fromEntries(
2965
+ Object.entries(filterValues).filter(([, value]) => value !== "" && value != null)
2966
+ );
2592
2967
  const listQuery = useQuery7({
2593
- queryKey: ["oon", "pipeline", basePath],
2594
- queryFn: () => resource.list({ pageSize: 200 }),
2595
- enabled: !!schema
2968
+ queryKey: ["oon", "pipeline", basePath, effectiveFilters],
2969
+ queryFn: () => resource.list({ pageSize: 200, ...effectiveFilters }),
2970
+ enabled: !!schema && viewMode === "board"
2596
2971
  });
2597
- const invalidate = () => queryClient.invalidateQueries({ queryKey: ["oon", "pipeline", basePath] });
2972
+ const invalidate = async () => {
2973
+ await Promise.all([
2974
+ queryClient.invalidateQueries({ queryKey: ["oon", "pipeline", basePath] }),
2975
+ queryClient.invalidateQueries({ queryKey: ["oon", "list", basePath] })
2976
+ ]);
2977
+ };
2598
2978
  const saveMutation = useMutation3({
2599
2979
  mutationFn: (values) => {
2600
2980
  const id = selectedTicket ? ticketId(selectedTicket) : void 0;
2601
- if (!id) throw new Error("N\xE3o foi poss\xEDvel identificar o ticket selecionado.");
2602
- return resource.update(id, values);
2981
+ return id ? resource.update(id, values) : resource.create(values);
2603
2982
  },
2604
2983
  onSuccess: async () => {
2605
2984
  await invalidate();
2606
2985
  setSelectedTicket(null);
2986
+ setCreating(false);
2607
2987
  setFormError(null);
2608
2988
  },
2609
2989
  onError: (error) => setFormError(error)
2610
2990
  });
2611
- if (schemaQuery.isLoading || listQuery.isLoading) return /* @__PURE__ */ jsx18(LoadingScreen, {});
2991
+ if (schemaQuery.isLoading || viewMode === "board" && listQuery.isLoading) return /* @__PURE__ */ jsx18(LoadingScreen, {});
2612
2992
  if (schemaQuery.isError) return /* @__PURE__ */ jsx18(ErrorState, { error: schemaQuery.error });
2613
- if (listQuery.isError) return /* @__PURE__ */ jsx18(ErrorState, { error: listQuery.error });
2993
+ if (viewMode === "board" && listQuery.isError) return /* @__PURE__ */ jsx18(ErrorState, { error: listQuery.error });
2614
2994
  const stageFieldMeta = schema?.fields.find((field) => field.name === stageField);
2615
2995
  const resolvedStages = stages ?? (stageFieldMeta?.options ?? []).map((option) => ({ id: option, label: option }));
2616
2996
  const resolvedTitleField = titleField ?? ticketModal?.titleField ?? schema?.fields.find((field) => field.kind === "string")?.name ?? schema?.searchable[0] ?? "_id";
2617
- const detailFields = (schema?.fields ?? []).filter((field) => field.name !== stageField && field.name !== resolvedTitleField && !["raw"].includes(field.kind)).slice(0, 3);
2997
+ const detailFields = (schema?.fields ?? []).filter((field) => field.name !== stageField && field.name !== resolvedTitleField && field.kind !== "raw").slice(0, 3);
2618
2998
  const formFields = mergeFormFields2(formFieldsFromMeta(schema?.fields ?? []), form);
2999
+ const resolvedListColumns = list?.columns?.length ? list.columns.map((column) => normalizeColumn(column, schema)) : columnsFromMeta(schema?.fields ?? []).slice(0, 10);
2619
3000
  const rows = listQuery.data?.results ?? [];
2620
3001
  const byStage = /* @__PURE__ */ new Map();
2621
3002
  for (const stage of resolvedStages) byStage.set(stage.id, []);
2622
3003
  for (const row of rows) {
2623
3004
  const key = String(row[stageField] ?? "");
2624
3005
  if (!byStage.has(key)) byStage.set(key, []);
2625
- byStage.get(key).push(row);
3006
+ byStage.get(key)?.push(row);
2626
3007
  }
3008
+ const openCreate = () => {
3009
+ const defaultStage = create?.defaultStage ?? resolvedStages[0]?.id;
3010
+ setFormError(null);
3011
+ setCreating(true);
3012
+ setSelectedTicket({
3013
+ ...create?.defaultValues ?? {},
3014
+ ...defaultStage ? { [stageField]: defaultStage } : {}
3015
+ });
3016
+ };
3017
+ const modalActions = selectedTicket && !creating ? /* @__PURE__ */ jsx18(Flex8, { gap: "5px", align: "center", wrap: "wrap", children: ticketActions.map((action, index) => /* @__PURE__ */ jsx18(
3018
+ CoreRowAction,
3019
+ {
3020
+ action,
3021
+ row: selectedTicket,
3022
+ onUpdate: async (values) => {
3023
+ const id = ticketId(selectedTicket);
3024
+ if (!id) throw new Error("Ticket sem identificador.");
3025
+ const saved = await resource.update(id, values);
3026
+ setSelectedTicket((current) => ({ ...current ?? {}, ...saved, ...values }));
3027
+ await invalidate();
3028
+ return saved;
3029
+ },
3030
+ onRefresh: () => {
3031
+ void invalidate();
3032
+ }
3033
+ },
3034
+ action.id ?? `${action.label}-${index}`
3035
+ )) }) : null;
2627
3036
  return /* @__PURE__ */ jsxs13(Stack5, { gap: 5, children: [
2628
- /* @__PURE__ */ jsxs13(Box10, { children: [
2629
- /* @__PURE__ */ jsx18(Text12, { fontSize: "11px", fontWeight: "700", color: "brand.500", textTransform: "uppercase", letterSpacing: "0.08em", mb: 1, children: "Esteira de processo" }),
2630
- /* @__PURE__ */ jsx18(Heading5, { size: "lg", color: "#24323A", letterSpacing: "-0.02em", children: label ?? schema?.name ?? model }),
2631
- /* @__PURE__ */ jsx18(Text12, { mt: 1, fontSize: "13px", color: "gray.500", children: "Acompanhe os tickets por etapa e visualize os principais dados de cada item." })
3037
+ /* @__PURE__ */ jsxs13(Flex8, { align: { base: "stretch", md: "flex-end" }, justify: "space-between", gap: 4, direction: { base: "column", md: "row" }, children: [
3038
+ /* @__PURE__ */ jsxs13(Box10, { children: [
3039
+ /* @__PURE__ */ jsx18(Text12, { fontSize: "11px", fontWeight: "700", color: "brand.500", textTransform: "uppercase", letterSpacing: "0.08em", mb: 1, children: "Esteira de processo" }),
3040
+ /* @__PURE__ */ jsx18(Heading5, { size: "lg", color: "#24323A", letterSpacing: "-0.02em", children: label ?? schema?.name ?? model }),
3041
+ /* @__PURE__ */ jsx18(Text12, { mt: 1, fontSize: "13px", color: "gray.500", children: "Acompanhe os tickets por etapa e visualize os principais dados de cada item." })
3042
+ ] }),
3043
+ /* @__PURE__ */ jsxs13(Flex8, { gap: "8px", align: "center", wrap: "wrap", children: [
3044
+ allowedViewModes.length > 1 ? allowedViewModes.map((mode) => /* @__PURE__ */ jsx18(Button9, { size: "sm", variant: viewMode === mode ? "solid" : "outline", colorPalette: "brand", onClick: () => setViewMode(mode), children: mode === "board" ? "Quadro" : "Lista" }, mode)) : null,
3045
+ create?.enabled ? /* @__PURE__ */ jsx18(Button9, { size: "sm", colorPalette: "brand", onClick: openCreate, children: create.label ?? "Novo ticket" }) : null
3046
+ ] })
2632
3047
  ] }),
2633
- /* @__PURE__ */ jsx18(Flex8, { gap: 4, align: "stretch", overflowX: "auto", pb: 3, minH: "420px", children: Array.from(byStage.entries()).map(([stageId, items]) => {
2634
- const stageLabel = resolvedStages.find((stage) => stage.id === stageId)?.label ?? (stageId || "Sem etapa");
2635
- return /* @__PURE__ */ jsxs13(
2636
- Box10,
3048
+ filters.length ? /* @__PURE__ */ jsxs13(Flex8, { p: "12px", gap: "10px", align: "flex-end", wrap: "wrap", bg: "white", borderWidth: "1px", borderColor: "#E5E9ED", borderRadius: "10px", children: [
3049
+ filters.map((filter) => {
3050
+ const meta = schema?.fields.find((field) => field.name === filter.field);
3051
+ return /* @__PURE__ */ jsxs13(Box10, { children: [
3052
+ /* @__PURE__ */ jsx18(Text12, { mb: "4px", fontSize: "10px", color: "#667085", fontWeight: "600", children: filter.label ?? fieldLabel({ name: filter.field }) }),
3053
+ /* @__PURE__ */ jsx18(
3054
+ PipelineFilterControl,
3055
+ {
3056
+ filter,
3057
+ value: filterValues[filter.field],
3058
+ meta,
3059
+ onChange: (value) => setFilterValues((current) => ({ ...current, [filter.field]: value }))
3060
+ }
3061
+ )
3062
+ ] }, filter.field);
3063
+ }),
3064
+ /* @__PURE__ */ jsx18(
3065
+ Button9,
2637
3066
  {
2638
- minW: "300px",
2639
- maxW: "340px",
2640
- flex: "1 0 300px",
2641
- bg: "#E8ECEF",
2642
- borderRadius: "12px",
2643
- p: 3,
2644
- borderWidth: "1px",
2645
- borderColor: "#DDE3E7",
2646
- children: [
2647
- /* @__PURE__ */ jsxs13(Flex8, { align: "center", justify: "space-between", mb: 3, px: 1, children: [
2648
- /* @__PURE__ */ jsx18(Text12, { fontWeight: "700", fontSize: "12px", color: "#33434C", textTransform: "uppercase", letterSpacing: "0.03em", children: stageLabel }),
2649
- /* @__PURE__ */ jsx18(Badge4, { colorPalette: "brand", variant: "solid", borderRadius: "full", minW: "24px", justifyContent: "center", children: items.length })
2650
- ] }),
2651
- /* @__PURE__ */ jsx18(Stack5, { gap: 2, children: items.length === 0 ? /* @__PURE__ */ jsx18(Box10, { borderWidth: "1px", borderStyle: "dashed", borderColor: "#C8D0D5", borderRadius: "10px", p: 5, textAlign: "center", children: /* @__PURE__ */ jsx18(Text12, { fontSize: "12px", color: "gray.500", children: "Nenhum ticket nesta etapa." }) }) : items.map((row, index) => {
2652
- const ticketTitle = formatCell(row[resolvedTitleField], "string");
2653
- return /* @__PURE__ */ jsxs13(
2654
- Button9,
2655
- {
2656
- type: "button",
2657
- variant: "plain",
2658
- display: "block",
2659
- w: "100%",
2660
- h: "auto",
2661
- minH: "auto",
2662
- p: 3,
2663
- bg: "white",
2664
- borderRadius: "10px",
2665
- borderWidth: "1px",
2666
- borderColor: "#DDE3E7",
2667
- boxShadow: "0 3px 10px rgba(7, 38, 46, 0.05)",
2668
- textAlign: "left",
2669
- whiteSpace: "normal",
2670
- transition: "all 0.15s ease",
2671
- "aria-label": `Abrir ticket ${ticketTitle}`,
2672
- onClick: () => {
2673
- setFormError(null);
2674
- setSelectedTicket(row);
2675
- },
2676
- _hover: { borderColor: "brand.300", transform: "translateY(-1px)", boxShadow: "0 7px 18px rgba(7, 38, 46, 0.09)" },
2677
- _focusVisible: { borderColor: "brand.500", boxShadow: "0 0 0 3px rgba(4, 116, 175, 0.16)" },
2678
- children: [
2679
- /* @__PURE__ */ jsx18(Text12, { fontSize: "13px", fontWeight: "700", color: "#24323A", mb: 2, lineClamp: 2, children: ticketTitle }),
2680
- /* @__PURE__ */ jsx18(Stack5, { gap: 1, children: detailFields.map((field) => /* @__PURE__ */ jsxs13(Flex8, { gap: 2, align: "baseline", justify: "space-between", children: [
2681
- /* @__PURE__ */ jsx18(Text12, { fontSize: "10px", color: "gray.500", textTransform: "uppercase", letterSpacing: "0.03em", children: fieldLabel(field) }),
2682
- /* @__PURE__ */ jsx18(Text12, { fontSize: "11px", color: "#46545C", fontWeight: "500", textAlign: "right", lineClamp: 1, children: formatCell(row[field.name], field.kind) })
2683
- ] }, field.name)) })
2684
- ]
2685
- },
2686
- ticketId(row) ?? index
2687
- );
2688
- }) })
2689
- ]
2690
- },
2691
- stageId
2692
- );
3067
+ size: "sm",
3068
+ variant: "outline",
3069
+ onClick: () => setFilterValues(Object.fromEntries(filters.map((filter) => [filter.field, filter.defaultValue ?? ""]))),
3070
+ children: "Limpar filtros"
3071
+ }
3072
+ )
3073
+ ] }) : null,
3074
+ viewMode === "list" ? /* @__PURE__ */ jsx18(
3075
+ DataGrid,
3076
+ {
3077
+ resource,
3078
+ resourceKey: basePath,
3079
+ columns: resolvedListColumns,
3080
+ filters: [],
3081
+ queryExtra: effectiveFilters,
3082
+ onEdit: (row) => {
3083
+ setCreating(false);
3084
+ setSelectedTicket(row);
3085
+ }
3086
+ }
3087
+ ) : /* @__PURE__ */ jsx18(Flex8, { gap: 4, align: "stretch", overflowX: "auto", pb: 3, minH: "420px", children: Array.from(byStage.entries()).map(([stageId, items]) => {
3088
+ const stageLabel = resolvedStages.find((stage) => stage.id === stageId)?.label ?? (stageId || "Sem etapa");
3089
+ return /* @__PURE__ */ jsxs13(Box10, { minW: "300px", maxW: "340px", flex: "1 0 300px", bg: "#E8ECEF", borderRadius: "12px", p: 3, borderWidth: "1px", borderColor: "#DDE3E7", children: [
3090
+ /* @__PURE__ */ jsxs13(Flex8, { align: "center", justify: "space-between", mb: 3, px: 1, children: [
3091
+ /* @__PURE__ */ jsx18(Text12, { fontWeight: "700", fontSize: "12px", color: "#33434C", textTransform: "uppercase", letterSpacing: "0.03em", children: stageLabel }),
3092
+ /* @__PURE__ */ jsx18(Badge4, { colorPalette: "brand", variant: "solid", borderRadius: "full", minW: "24px", justifyContent: "center", children: items.length })
3093
+ ] }),
3094
+ /* @__PURE__ */ jsx18(Stack5, { gap: 2, children: items.length === 0 ? /* @__PURE__ */ jsx18(Box10, { borderWidth: "1px", borderStyle: "dashed", borderColor: "#C8D0D5", borderRadius: "10px", p: 5, textAlign: "center", children: /* @__PURE__ */ jsx18(Text12, { fontSize: "12px", color: "gray.500", children: "Nenhum ticket nesta etapa." }) }) : items.map((row, index) => {
3095
+ const ticketTitle = formatCell(row[resolvedTitleField], "string");
3096
+ return /* @__PURE__ */ jsxs13(
3097
+ Button9,
3098
+ {
3099
+ type: "button",
3100
+ variant: "plain",
3101
+ display: "block",
3102
+ w: "100%",
3103
+ h: "auto",
3104
+ minH: "auto",
3105
+ p: 3,
3106
+ bg: "white",
3107
+ borderRadius: "10px",
3108
+ borderWidth: "1px",
3109
+ borderColor: "#DDE3E7",
3110
+ boxShadow: "0 3px 10px rgba(7, 38, 46, 0.05)",
3111
+ textAlign: "left",
3112
+ whiteSpace: "normal",
3113
+ "aria-label": `Abrir ticket ${ticketTitle}`,
3114
+ onClick: () => {
3115
+ setFormError(null);
3116
+ setCreating(false);
3117
+ setSelectedTicket(row);
3118
+ },
3119
+ _hover: { borderColor: "brand.300", transform: "translateY(-1px)", boxShadow: "0 7px 18px rgba(7, 38, 46, 0.09)" },
3120
+ children: [
3121
+ /* @__PURE__ */ jsx18(Text12, { fontSize: "13px", fontWeight: "700", color: "#24323A", mb: 2, lineClamp: 2, children: ticketTitle }),
3122
+ /* @__PURE__ */ jsx18(Stack5, { gap: 1, children: detailFields.map((field) => /* @__PURE__ */ jsxs13(Flex8, { gap: 2, align: "baseline", justify: "space-between", children: [
3123
+ /* @__PURE__ */ jsx18(Text12, { fontSize: "10px", color: "gray.500", textTransform: "uppercase", letterSpacing: "0.03em", children: fieldLabel(field) }),
3124
+ /* @__PURE__ */ jsx18(Text12, { fontSize: "11px", color: "#46545C", fontWeight: "500", textAlign: "right", lineClamp: 1, children: formatCell(row[field.name], field.kind) })
3125
+ ] }, field.name)) })
3126
+ ]
3127
+ },
3128
+ ticketId(row) ?? index
3129
+ );
3130
+ }) })
3131
+ ] }, stageId);
2693
3132
  }) }),
2694
3133
  selectedTicket && ticketModal && ticketModal.enabled !== false ? /* @__PURE__ */ jsx18(
2695
3134
  CoreDetailModal,
@@ -2698,19 +3137,28 @@ function CorePipeline({
2698
3137
  title: schema?.singular ?? label ?? model,
2699
3138
  config: { ...ticketModal, titleField: ticketModal.titleField ?? resolvedTitleField },
2700
3139
  formFields,
2701
- recordId: ticketId(selectedTicket),
3140
+ recordId: creating ? void 0 : ticketId(selectedTicket),
2702
3141
  initialRecord: selectedTicket,
3142
+ initialTab: creating ? create?.initialTab : void 0,
2703
3143
  relations,
3144
+ actions: modalActions,
2704
3145
  onClose: () => {
2705
3146
  setSelectedTicket(null);
3147
+ setCreating(false);
2706
3148
  setFormError(null);
2707
3149
  },
2708
- onSaved: invalidate
3150
+ onSaved: async () => {
3151
+ await invalidate();
3152
+ if (creating) {
3153
+ setSelectedTicket(null);
3154
+ setCreating(false);
3155
+ }
3156
+ }
2709
3157
  }
2710
3158
  ) : selectedTicket ? /* @__PURE__ */ jsx18(
2711
3159
  DynamicForm,
2712
3160
  {
2713
- title: `Editar ${schema?.singular ?? label ?? model}`,
3161
+ title: `${creating ? "Novo" : "Editar"} ${schema?.singular ?? label ?? model}`,
2714
3162
  eyebrow: "Ticket",
2715
3163
  fields: formFields,
2716
3164
  initialValues: selectedTicket,
@@ -2722,9 +3170,11 @@ function CorePipeline({
2722
3170
  },
2723
3171
  onFieldChange: () => setFormError(null),
2724
3172
  onCancel: () => {
2725
- if (saveMutation.isPending) return;
2726
- setSelectedTicket(null);
2727
- setFormError(null);
3173
+ if (!saveMutation.isPending) {
3174
+ setSelectedTicket(null);
3175
+ setCreating(false);
3176
+ setFormError(null);
3177
+ }
2728
3178
  }
2729
3179
  }
2730
3180
  ) : null
@@ -2980,7 +3430,13 @@ function renderView(view, registry) {
2980
3430
  titleField: v.titleField,
2981
3431
  form: v.form,
2982
3432
  relations: v.relations,
2983
- ticketModal: v.ticketModal
3433
+ ticketModal: v.ticketModal,
3434
+ filters: v.filters,
3435
+ create: v.create,
3436
+ viewModes: v.viewModes,
3437
+ defaultView: v.defaultView,
3438
+ list: v.list,
3439
+ ticketActions: v.ticketActions
2984
3440
  }
2985
3441
  );
2986
3442
  }
@@ -3345,7 +3801,7 @@ import {
3345
3801
  Button as Button13,
3346
3802
  Flex as Flex13,
3347
3803
  Heading as Heading10,
3348
- Input as Input6,
3804
+ Input as Input7,
3349
3805
  Text as Text20,
3350
3806
  VStack as VStack2
3351
3807
  } from "@chakra-ui/react";
@@ -3420,7 +3876,7 @@ function LoginPage({ app }) {
3420
3876
  /* @__PURE__ */ jsxs21(Box18, { children: [
3421
3877
  /* @__PURE__ */ jsx30(Text20, { mb: 2, fontSize: "sm", fontWeight: "600", color: "#24323A", children: "E-mail" }),
3422
3878
  /* @__PURE__ */ jsx30(
3423
- Input6,
3879
+ Input7,
3424
3880
  {
3425
3881
  type: "email",
3426
3882
  value: email,
@@ -3434,7 +3890,7 @@ function LoginPage({ app }) {
3434
3890
  /* @__PURE__ */ jsxs21(Box18, { children: [
3435
3891
  /* @__PURE__ */ jsx30(Text20, { mb: 2, fontSize: "sm", fontWeight: "600", color: "#24323A", children: "Senha" }),
3436
3892
  /* @__PURE__ */ jsx30(
3437
- Input6,
3893
+ Input7,
3438
3894
  {
3439
3895
  type: "password",
3440
3896
  value: password,
@@ -3477,7 +3933,7 @@ function LoginPage({ app }) {
3477
3933
 
3478
3934
  // src/security/ActivationPage.tsx
3479
3935
  import { useEffect as useEffect7, useState as useState13 } from "react";
3480
- import { Box as Box19, Button as Button14, Heading as Heading11, Input as Input7, Text as Text21, VStack as VStack3 } from "@chakra-ui/react";
3936
+ import { Box as Box19, Button as Button14, Heading as Heading11, Input as Input8, Text as Text21, VStack as VStack3 } from "@chakra-ui/react";
3481
3937
  import { Fragment as Fragment6, jsx as jsx31, jsxs as jsxs22 } from "react/jsx-runtime";
3482
3938
  function encodeBasicCredentials2(email, password) {
3483
3939
  const bytes = new TextEncoder().encode(`${email}:${password}`);
@@ -3618,7 +4074,7 @@ function ActivationPage() {
3618
4074
  field.required ? " *" : ""
3619
4075
  ] }),
3620
4076
  /* @__PURE__ */ jsx31(
3621
- Input7,
4077
+ Input8,
3622
4078
  {
3623
4079
  type: field.type === "password" || field.type === "secret" ? "password" : field.type === "email" ? "email" : field.type === "url" ? "url" : field.type === "number" || field.type === "integer" ? "number" : "text",
3624
4080
  value: String(values[field.name] ?? field.defaultValue ?? ""),
@@ -3638,7 +4094,7 @@ function ActivationPage() {
3638
4094
  /* @__PURE__ */ jsxs22(Box19, { w: "100%", children: [
3639
4095
  /* @__PURE__ */ jsx31(Text21, { fontWeight: "medium", children: "Nome do usu\xE1rio" }),
3640
4096
  /* @__PURE__ */ jsx31(
3641
- Input7,
4097
+ Input8,
3642
4098
  {
3643
4099
  value: devName,
3644
4100
  onChange: (event) => setDevName(event.target.value),
@@ -3650,7 +4106,7 @@ function ActivationPage() {
3650
4106
  /* @__PURE__ */ jsxs22(Box19, { w: "100%", children: [
3651
4107
  /* @__PURE__ */ jsx31(Text21, { fontWeight: "medium", children: "E-mail *" }),
3652
4108
  /* @__PURE__ */ jsx31(
3653
- Input7,
4109
+ Input8,
3654
4110
  {
3655
4111
  type: "email",
3656
4112
  value: devEmail,
@@ -3663,7 +4119,7 @@ function ActivationPage() {
3663
4119
  /* @__PURE__ */ jsxs22(Box19, { w: "100%", children: [
3664
4120
  /* @__PURE__ */ jsx31(Text21, { fontWeight: "medium", children: "Senha *" }),
3665
4121
  /* @__PURE__ */ jsx31(
3666
- Input7,
4122
+ Input8,
3667
4123
  {
3668
4124
  type: "password",
3669
4125
  value: devPassword,
@@ -3741,7 +4197,7 @@ function ActivationPage() {
3741
4197
  /* @__PURE__ */ jsxs22(Box19, { w: "100%", children: [
3742
4198
  /* @__PURE__ */ jsx31(Text21, { fontWeight: "medium", children: "E-mail do administrador *" }),
3743
4199
  /* @__PURE__ */ jsx31(
3744
- Input7,
4200
+ Input8,
3745
4201
  {
3746
4202
  type: "email",
3747
4203
  autoComplete: "email",
@@ -3754,7 +4210,7 @@ function ActivationPage() {
3754
4210
  /* @__PURE__ */ jsxs22(Box19, { w: "100%", children: [
3755
4211
  /* @__PURE__ */ jsx31(Text21, { fontWeight: "medium", children: "Senha *" }),
3756
4212
  /* @__PURE__ */ jsx31(
3757
- Input7,
4213
+ Input8,
3758
4214
  {
3759
4215
  type: "password",
3760
4216
  minLength: 8,
@@ -3810,7 +4266,7 @@ function ActivationPage() {
3810
4266
  step === 1 && (!isDevelopmentMember || showCodeFlow) && /* @__PURE__ */ jsxs22(Fragment6, { children: [
3811
4267
  /* @__PURE__ */ jsx31(Text21, { children: "Informe o c\xF3digo de ativa\xE7\xE3o para validar tenant, aplicativo e licen\xE7a antes de consumir o c\xF3digo." }),
3812
4268
  /* @__PURE__ */ jsx31(
3813
- Input7,
4269
+ Input8,
3814
4270
  {
3815
4271
  "aria-label": "C\xF3digo de ativa\xE7\xE3o",
3816
4272
  value: code,
@@ -4112,7 +4568,13 @@ function manifestToConfig(manifest, runtime) {
4112
4568
  section: p.section,
4113
4569
  form: p.form,
4114
4570
  relations: p.relations,
4115
- ticketModal: p.ticketModal
4571
+ ticketModal: p.ticketModal,
4572
+ filters: p.filters,
4573
+ create: p.create,
4574
+ viewModes: p.viewModes,
4575
+ defaultView: p.defaultView,
4576
+ list: p.list,
4577
+ ticketActions: p.ticketActions
4116
4578
  };
4117
4579
  views.push(view);
4118
4580
  }
@@ -4247,7 +4709,7 @@ function CoreCurrency({ model = "Moeda", label = "Multi-moedas", endpoint = "/mo
4247
4709
 
4248
4710
  // src/components/CoreAssistant.tsx
4249
4711
  import { useState as useState14 } from "react";
4250
- import { Box as Box21, Button as Button17, Flex as Flex16, Heading as Heading14, Input as Input8, Stack as Stack11, Text as Text24 } from "@chakra-ui/react";
4712
+ import { Box as Box21, Button as Button17, Flex as Flex16, Heading as Heading14, Input as Input9, Stack as Stack11, Text as Text24 } from "@chakra-ui/react";
4251
4713
  import { useMutation as useMutation6 } from "@tanstack/react-query";
4252
4714
  import { jsx as jsx39, jsxs as jsxs25 } from "react/jsx-runtime";
4253
4715
  function CoreAssistant({ assistantCode, label = "Assistente", entity, endpoint = "/assistant-runs" }) {
@@ -4299,7 +4761,7 @@ function CoreAssistant({ assistantCode, label = "Assistente", entity, endpoint =
4299
4761
  ] }) }),
4300
4762
  /* @__PURE__ */ jsxs25(Flex16, { gap: 2, children: [
4301
4763
  /* @__PURE__ */ jsx39(
4302
- Input8,
4764
+ Input9,
4303
4765
  {
4304
4766
  size: "sm",
4305
4767
  value: input,