@oondemand/oon-core-front 0.3.12 → 0.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -246,6 +246,7 @@ interface OonColumnDef {
246
246
  permissions?: string[];
247
247
  capabilities?: string[];
248
248
  }
249
+ type OonFormFieldWidget = "default" | "checkbox";
249
250
  interface OonFormFieldDef {
250
251
  field: string;
251
252
  label?: string;
@@ -255,6 +256,19 @@ interface OonFormFieldDef {
255
256
  /** model relacionada quando kind === "ref". */
256
257
  ref?: string;
257
258
  renderer?: string;
259
+ /** Controle visual pontual sem substituir a página/formulário inteiro. */
260
+ widget?: OonFormFieldWidget;
261
+ /** Valor persistido quando um checkbox está marcado. Default: true. */
262
+ checkedValue?: string | number | boolean;
263
+ /** Valor persistido quando um checkbox está desmarcado. Default: false. */
264
+ uncheckedValue?: string | number | boolean;
265
+ /** Agrupa campos relacionados em um bloco visual comum. */
266
+ group?: string;
267
+ /** Rótulo exibido uma vez acima do grupo. Default: group. */
268
+ groupLabel?: string;
269
+ groupDescription?: string;
270
+ /** Exibe controles do mesmo grupo na mesma linha quando houver espaço. */
271
+ inline?: boolean;
258
272
  permissions?: string[];
259
273
  capabilities?: string[];
260
274
  }
@@ -412,7 +426,7 @@ interface CollectionViewDef {
412
426
  mode?: CollectionMode;
413
427
  /** modo "full": colunas explícitas. */
414
428
  columns?: OonColumnDef[];
415
- /** modo "full": campos de formulário explícitos. */
429
+ /** Campos explícitos ou overrides pontuais sobre a metadata do model. */
416
430
  form?: OonFormFieldDef[];
417
431
  importExport?: boolean;
418
432
  renderer?: string;
@@ -583,6 +597,8 @@ interface CentralUiManifest {
583
597
  path?: string;
584
598
  label?: string;
585
599
  section?: string;
600
+ /** Overrides pontuais de campos; os demais continuam vindo da metadata. */
601
+ form?: OonFormFieldDef[];
586
602
  list?: OonCollectionListConfig;
587
603
  relations?: Record<string, OonRelationDef>;
588
604
  detailModal?: OonDetailModalConfig;
@@ -658,8 +674,9 @@ interface CoreCollectionProps {
658
674
  list?: OonCollectionListConfig;
659
675
  relations?: Record<string, OonRelationDef>;
660
676
  detailModal?: OonDetailModalConfig;
677
+ cellRenderers?: OonComponentRegistry["cellRenderers"];
661
678
  }
662
- declare function CoreCollection({ model, label, section, mode, endpoint, columns, form, importExport, list, relations, detailModal }: CoreCollectionProps): react.JSX.Element;
679
+ declare function CoreCollection({ model, label, section, mode, endpoint, columns, form, importExport, list, relations, detailModal, cellRenderers }: CoreCollectionProps): react.JSX.Element;
663
680
 
664
681
  interface CoreDocumentProps {
665
682
  model: string;
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { StrictMode } from "react";
3
3
  import { createRoot } from "react-dom/client";
4
4
 
5
5
  // src/app/OonApp.tsx
6
- import { useMemo as useMemo6 } from "react";
6
+ import { useMemo as useMemo7 } from "react";
7
7
  import { RouterProvider } from "react-router-dom";
8
8
 
9
9
  // src/api/createRestClient.ts
@@ -787,6 +787,12 @@ function renderCell(value, kind) {
787
787
  }
788
788
  return formatted;
789
789
  }
790
+ function renderColumnCell(row, column, cellRenderers) {
791
+ const value = row[column.field];
792
+ const Renderer = column.renderer ? cellRenderers?.[column.renderer] : void 0;
793
+ if (Renderer) return /* @__PURE__ */ jsx7(Renderer, { value, row, column });
794
+ return renderCell(value, column.kind);
795
+ }
790
796
  function DataGrid({
791
797
  resource,
792
798
  resourceKey,
@@ -798,7 +804,8 @@ function DataGrid({
798
804
  toolbarExtra,
799
805
  filters = [],
800
806
  queryExtra,
801
- renderRowActions
807
+ renderRowActions,
808
+ cellRenderers
802
809
  }) {
803
810
  const [pageIndex, setPageIndex] = useState3(0);
804
811
  const [pageSize, setPageSize] = useState3(20);
@@ -904,7 +911,7 @@ function DataGrid({
904
911
  actionCount ? /* @__PURE__ */ jsx7(Table.ColumnHeader, { w: "84px", h: "39px", px: "10px", py: "8px", color: "#667085", borderBottomWidth: "1px", borderColor: "#D5DDE3", fontSize: "10px", fontWeight: "600", textAlign: "center", children: "A\xE7\xF5es" }) : null
905
912
  ] }) }),
906
913
  /* @__PURE__ */ jsx7(Table.Body, { children: rows.length === 0 ? /* @__PURE__ */ jsx7(Table.Row, { children: /* @__PURE__ */ jsx7(Table.Cell, { colSpan: columns.length + actionCount, children: /* @__PURE__ */ jsx7(Text3, { minH: "140px", display: "grid", placeItems: "center", color: "#98A2B3", fontSize: "12px", children: "Nenhum registro encontrado." }) }) }) : rows.map((row, index) => /* @__PURE__ */ jsxs3(Table.Row, { bg: index % 2 ? "#FBFCFD" : "white", _hover: { bg: "#F0F7FF" }, children: [
907
- columns.map((column) => /* @__PURE__ */ jsx7(Table.Cell, { maxW: "250px", h: "42px", px: "10px", py: "8px", overflow: "hidden", color: "#52606D", borderBottomWidth: "1px", borderColor: "#EEF1F3", fontSize: "11px", textOverflow: "ellipsis", whiteSpace: "nowrap", children: renderCell(row[column.field], column.kind) }, column.field)),
914
+ columns.map((column) => /* @__PURE__ */ jsx7(Table.Cell, { maxW: "250px", h: "42px", px: "10px", py: "8px", overflow: "hidden", color: "#52606D", borderBottomWidth: "1px", borderColor: "#EEF1F3", fontSize: "11px", textOverflow: "ellipsis", whiteSpace: "nowrap", children: renderColumnCell(row, column, cellRenderers) }, column.field)),
908
915
  actionCount ? /* @__PURE__ */ jsx7(Table.Cell, { h: "42px", px: "10px", py: "5px", borderBottomWidth: "1px", borderColor: "#EEF1F3", textAlign: "center", children: /* @__PURE__ */ jsxs3(Flex2, { justify: "center", gap: "5px", children: [
909
916
  onEdit ? /* @__PURE__ */ jsx7(Button2, { type: "button", "aria-label": "Editar", w: "31px", minW: "31px", h: "31px", p: 0, color: "#667085", bg: "white", borderWidth: "1px", borderColor: "#E5E9ED", borderRadius: "6px", onClick: () => onEdit(row), _hover: { color: "#036491", bg: "#F0F7FF", borderColor: "#B8D9EB" }, children: /* @__PURE__ */ jsx7(EditIcon, {}) }) : null,
910
917
  onDelete ? /* @__PURE__ */ jsx7(Button2, { type: "button", "aria-label": "Excluir", w: "31px", minW: "31px", h: "31px", p: 0, color: "#667085", bg: "white", borderWidth: "1px", borderColor: "#E5E9ED", borderRadius: "6px", onClick: () => onDelete(row), _hover: { color: "#B42318", bg: "#FFF5F5", borderColor: "#FECACA" }, children: /* @__PURE__ */ jsx7(TrashIcon, {}) }) : null,
@@ -953,7 +960,7 @@ function DataGrid({
953
960
  }
954
961
 
955
962
  // src/components/internal/DynamicForm.tsx
956
- import { useEffect as useEffect4, useState as useState5 } from "react";
963
+ import { useEffect as useEffect4, useMemo as useMemo4, useState as useState5 } from "react";
957
964
  import { Box as Box4, Button as Button4, Flex as Flex4, Heading as Heading2, Input as Input3, NativeSelect as NativeSelect2, SimpleGrid, Stack as Stack2, Text as Text5, Textarea } from "@chakra-ui/react";
958
965
 
959
966
  // src/components/internal/ReferenceSelect.tsx
@@ -1443,10 +1450,25 @@ var controlProps = (invalid) => ({
1443
1450
  boxShadow: invalid ? "0 0 0 3px rgba(220,38,38,.08)" : void 0,
1444
1451
  fontSize: "11px"
1445
1452
  });
1453
+ function buildItems(fields) {
1454
+ const items = [];
1455
+ const renderedGroups = /* @__PURE__ */ new Set();
1456
+ fields.forEach((field) => {
1457
+ if (!field.group) {
1458
+ items.push({ type: "field", field });
1459
+ return;
1460
+ }
1461
+ if (renderedGroups.has(field.group)) return;
1462
+ renderedGroups.add(field.group);
1463
+ items.push({ type: "group", id: field.group, fields: fields.filter((candidate) => candidate.group === field.group) });
1464
+ });
1465
+ return items;
1466
+ }
1446
1467
  function DynamicForm({ title, eyebrow = "Cadastro", fields, initialValues, submitting, error, onSubmit, onCancel, onFieldChange }) {
1447
1468
  const [values, setValues] = useState5(() => Object.fromEntries(fields.map((field) => [field.field, toValue(initialValues?.[field.field], field.kind)])));
1448
1469
  const [errors, setErrors] = useState5({});
1449
1470
  const [dismissed, setDismissed] = useState5(false);
1471
+ const items = useMemo4(() => buildItems(fields), [fields]);
1450
1472
  useEffect4(() => {
1451
1473
  const listener = (event) => {
1452
1474
  if (event.key !== "Escape" || event.defaultPrevented || submitting) return;
@@ -1496,6 +1518,83 @@ function DynamicForm({ title, eyebrow = "Cadastro", fields, initialValues, submi
1496
1518
  setErrors({});
1497
1519
  onSubmit(payload);
1498
1520
  };
1521
+ const renderField = (field, grouped = false) => {
1522
+ const label = fieldLabel({ name: field.field, label: field.label });
1523
+ const message = errors[field.field];
1524
+ const id = idFor(field.field);
1525
+ const errorId = message ? errorIdFor(field.field) : void 0;
1526
+ const multiline = isMultiline(field);
1527
+ const span = field.span === 2 || multiline;
1528
+ const wrap = (control, showLabel = true) => /* @__PURE__ */ jsxs5(Box4, { minW: grouped ? "150px" : void 0, flex: grouped ? "0 1 auto" : void 0, gridColumn: !grouped && span ? "1 / -1" : void 0, children: [
1529
+ showLabel ? /* @__PURE__ */ jsx9(Label, { id, label, required: field.required }) : null,
1530
+ control,
1531
+ /* @__PURE__ */ jsx9(ErrorText, { id: errorId, text: message })
1532
+ ] }, field.field);
1533
+ if (field.widget === "checkbox") {
1534
+ const checkedValue = String(field.checkedValue ?? true);
1535
+ const uncheckedValue = String(field.uncheckedValue ?? false);
1536
+ const checked = (values[field.field] ?? uncheckedValue) === checkedValue;
1537
+ return wrap(
1538
+ /* @__PURE__ */ jsxs5(
1539
+ Box4,
1540
+ {
1541
+ as: "label",
1542
+ display: "flex",
1543
+ minH: "37px",
1544
+ px: "11px",
1545
+ alignItems: "center",
1546
+ gap: "8px",
1547
+ color: "#344054",
1548
+ bg: checked ? "#F0F7FF" : "white",
1549
+ borderWidth: "1px",
1550
+ borderColor: message ? "#DC2626" : checked ? "#8FC9E7" : "#D5DDE3",
1551
+ borderRadius: "7px",
1552
+ cursor: "pointer",
1553
+ transition: "background .15s ease, border-color .15s ease",
1554
+ _hover: { borderColor: "#8FC9E7", bg: "#F7FBFE" },
1555
+ children: [
1556
+ /* @__PURE__ */ jsx9(
1557
+ "input",
1558
+ {
1559
+ id,
1560
+ type: "checkbox",
1561
+ checked,
1562
+ "aria-invalid": !!message,
1563
+ "aria-describedby": errorId,
1564
+ style: { width: 16, height: 16, accentColor: "#0474AF", cursor: "pointer" },
1565
+ onChange: (event) => change(field.field, event.currentTarget.checked ? checkedValue : uncheckedValue)
1566
+ }
1567
+ ),
1568
+ /* @__PURE__ */ jsxs5(Text5, { as: "span", fontSize: "11px", fontWeight: "600", children: [
1569
+ label,
1570
+ field.required ? /* @__PURE__ */ jsx9(Text5, { as: "span", color: "#DC2626", children: " *" }) : null
1571
+ ] })
1572
+ ]
1573
+ }
1574
+ ),
1575
+ false
1576
+ );
1577
+ }
1578
+ if (field.kind === "ref" && field.ref) 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, onChange: (value) => change(field.field, value) }));
1579
+ if (field.kind === "enum" && field.options?.length) return wrap(/* @__PURE__ */ jsxs5(NativeSelect2.Root, { size: "sm", children: [
1580
+ /* @__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: [
1581
+ /* @__PURE__ */ jsx9("option", { value: "", children: "\u2014 Selecione \u2014" }),
1582
+ field.options.map((option) => /* @__PURE__ */ jsx9("option", { value: option, children: option }, option))
1583
+ ] }),
1584
+ /* @__PURE__ */ jsx9(NativeSelect2.Indicator, {})
1585
+ ] }));
1586
+ if (field.kind === "boolean") return wrap(/* @__PURE__ */ jsxs5(NativeSelect2.Root, { size: "sm", children: [
1587
+ /* @__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: [
1588
+ /* @__PURE__ */ jsx9("option", { value: "", children: "\u2014 Selecione \u2014" }),
1589
+ /* @__PURE__ */ jsx9("option", { value: "true", children: "Sim" }),
1590
+ /* @__PURE__ */ jsx9("option", { value: "false", children: "N\xE3o" })
1591
+ ] }),
1592
+ /* @__PURE__ */ jsx9(NativeSelect2.Indicator, {})
1593
+ ] }));
1594
+ 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) }));
1595
+ const type = field.kind === "date" ? "date" : ["number", "currency", "currencyConverted"].includes(field.kind ?? "") ? "number" : "text";
1596
+ 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) }));
1597
+ };
1499
1598
  const invalid = fields.filter((field) => errors[field.field]);
1500
1599
  const generic = dismissed ? null : messageOf(error);
1501
1600
  return /* @__PURE__ */ jsx9(Flex4, { position: "fixed", inset: 0, zIndex: 1e3, align: "center", justify: "center", p: { base: 2, md: 6 }, bg: "rgba(15,23,42,.48)", backdropFilter: "blur(2px)", onMouseDown: (event) => {
@@ -1525,38 +1624,15 @@ function DynamicForm({ title, eyebrow = "Cadastro", fields, initialValues, submi
1525
1624
  /* @__PURE__ */ jsx9(AlertIcon, {}),
1526
1625
  generic
1527
1626
  ] }) : null,
1528
- /* @__PURE__ */ jsx9(SimpleGrid, { columns: { base: 1, lg: 2 }, gapX: "16px", gapY: "15px", children: fields.map((baseField) => {
1529
- const field = baseField;
1530
- const label = fieldLabel({ name: field.field, label: field.label });
1531
- const message = errors[field.field];
1532
- const id = idFor(field.field);
1533
- const errorId = message ? errorIdFor(field.field) : void 0;
1534
- const multiline = isMultiline(field);
1535
- const span = field.span === 2 || multiline;
1536
- const wrap = (control) => /* @__PURE__ */ jsxs5(Box4, { gridColumn: span ? "1 / -1" : void 0, children: [
1537
- /* @__PURE__ */ jsx9(Label, { id, label, required: field.required }),
1538
- control,
1539
- /* @__PURE__ */ jsx9(ErrorText, { id: errorId, text: message })
1540
- ] }, field.field);
1541
- if (field.kind === "ref" && field.ref) 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, onChange: (value) => change(field.field, value) }));
1542
- if (field.kind === "enum" && field.options?.length) return wrap(/* @__PURE__ */ jsxs5(NativeSelect2.Root, { size: "sm", children: [
1543
- /* @__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: [
1544
- /* @__PURE__ */ jsx9("option", { value: "", children: "\u2014 Selecione \u2014" }),
1545
- field.options.map((option) => /* @__PURE__ */ jsx9("option", { value: option, children: option }, option))
1546
- ] }),
1547
- /* @__PURE__ */ jsx9(NativeSelect2.Indicator, {})
1548
- ] }));
1549
- if (field.kind === "boolean") return wrap(/* @__PURE__ */ jsxs5(NativeSelect2.Root, { size: "sm", children: [
1550
- /* @__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: [
1551
- /* @__PURE__ */ jsx9("option", { value: "", children: "\u2014 Selecione \u2014" }),
1552
- /* @__PURE__ */ jsx9("option", { value: "true", children: "Sim" }),
1553
- /* @__PURE__ */ jsx9("option", { value: "false", children: "N\xE3o" })
1554
- ] }),
1555
- /* @__PURE__ */ jsx9(NativeSelect2.Indicator, {})
1556
- ] }));
1557
- 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) }));
1558
- const type = field.kind === "date" ? "date" : ["number", "currency", "currencyConverted"].includes(field.kind ?? "") ? "number" : "text";
1559
- 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) }));
1627
+ /* @__PURE__ */ jsx9(SimpleGrid, { columns: { base: 1, lg: 2 }, gapX: "16px", gapY: "15px", children: items.map((item) => {
1628
+ if (item.type === "field") return renderField(item.field);
1629
+ const first = item.fields[0];
1630
+ const inline = item.fields.some((field) => field.inline !== false);
1631
+ return /* @__PURE__ */ jsxs5(Box4, { gridColumn: "1 / -1", p: "12px", bg: "#FAFCFD", borderWidth: "1px", borderColor: "#E5E9ED", borderRadius: "9px", children: [
1632
+ /* @__PURE__ */ jsx9(Text5, { color: "#344054", fontSize: "11px", fontWeight: "700", children: first.groupLabel ?? item.id }),
1633
+ first.groupDescription ? /* @__PURE__ */ jsx9(Text5, { mt: "2px", mb: "10px", color: "#667085", fontSize: "10px", children: first.groupDescription }) : /* @__PURE__ */ jsx9(Box4, { h: "8px" }),
1634
+ /* @__PURE__ */ jsx9(Flex4, { direction: inline ? "row" : "column", wrap: "wrap", gap: "9px", children: item.fields.map((field) => renderField(field, true)) })
1635
+ ] }, item.id);
1560
1636
  }) })
1561
1637
  ] }) }),
1562
1638
  /* @__PURE__ */ jsxs5(Flex4, { minH: "68px", px: "20px", py: "14px", justify: "flex-end", gap: "9px", borderTopWidth: "1px", borderColor: "#E5E9ED", children: [
@@ -1571,7 +1647,7 @@ function DynamicForm({ title, eyebrow = "Cadastro", fields, initialValues, submi
1571
1647
  }
1572
1648
 
1573
1649
  // src/components/internal/CoreDetailModal.tsx
1574
- import { useEffect as useEffect6, useMemo as useMemo5, useState as useState9 } from "react";
1650
+ import { useEffect as useEffect6, useMemo as useMemo6, useState as useState9 } from "react";
1575
1651
  import { Box as Box8, Button as Button8, Flex as Flex7, Heading as Heading3, Text as Text10 } from "@chakra-ui/react";
1576
1652
  import { useQuery as useQuery6, useQueryClient as useQueryClient2 } from "@tanstack/react-query";
1577
1653
 
@@ -1632,7 +1708,7 @@ function CoreSummaryCards({ cards = [], parent, relations }) {
1632
1708
  }
1633
1709
 
1634
1710
  // src/components/internal/CoreRelatedGrid.tsx
1635
- import { useMemo as useMemo4, useState as useState7 } from "react";
1711
+ import { useMemo as useMemo5, useState as useState7 } from "react";
1636
1712
  import { Badge as Badge3, Box as Box6, Button as Button6, Flex as Flex5, Table as Table2, Text as Text8 } from "@chakra-ui/react";
1637
1713
  import { useMutation, useQuery as useQuery5, useQueryClient } from "@tanstack/react-query";
1638
1714
 
@@ -1748,7 +1824,7 @@ function CoreRelatedGrid({ tab, parent, relations, refreshToken, onRefresh }) {
1748
1824
  const parentValue = relation ? parent[relation.parentKey ?? "_id"] : void 0;
1749
1825
  const key = ["oon", "related", relation?.model, relation?.foreignKey, parentValue, refreshToken];
1750
1826
  const query = useQuery5({ queryKey: key, enabled: !!relation && parentValue != null, queryFn: () => resource.list({ [relation.foreignKey]: parentValue, pageSize: 1e3 }) });
1751
- const columns = useMemo4(() => (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]);
1827
+ 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]);
1752
1828
  const [drafts, setDrafts] = useState7({});
1753
1829
  const [errors, setErrors] = useState7({});
1754
1830
  const editable = tab.type === "relatedGrid" && tab.editable && tab.editMode === "inline";
@@ -1876,7 +1952,7 @@ function CoreDetailModal({ model, title, config, recordId, initialRecord, initia
1876
1952
  useEffect6(() => {
1877
1953
  if (parentQuery.data) setParent(parentQuery.data);
1878
1954
  }, [parentQuery.data]);
1879
- const formFields = useMemo5(() => schema ? formFieldsFromMeta(schema.fields) : [], [schema]);
1955
+ const formFields = useMemo6(() => schema ? formFieldsFromMeta(schema.fields) : [], [schema]);
1880
1956
  const refresh = (targets) => {
1881
1957
  const list = targets?.length ? targets : ["self"];
1882
1958
  if (list.includes("parent") || list.includes("all")) {
@@ -1912,13 +1988,24 @@ function CoreDetailModal({ model, title, config, recordId, initialRecord, initia
1912
1988
 
1913
1989
  // src/components/CoreCollection.tsx
1914
1990
  import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
1915
- function CoreCollection({ model, label, section = "Cole\xE7\xE3o", mode = "dynamic", endpoint, columns, form, importExport, list, relations, detailModal }) {
1991
+ function mergeFormFields(metadataFields, overrides) {
1992
+ if (!overrides?.length) return metadataFields;
1993
+ const byField = new Map(overrides.map((field) => [field.field, field]));
1994
+ const merged = metadataFields.map((field) => ({ ...field, ...byField.get(field.field) }));
1995
+ const known = new Set(metadataFields.map((field) => field.field));
1996
+ overrides.forEach((field) => {
1997
+ if (!known.has(field.field)) merged.push(field);
1998
+ });
1999
+ return merged;
2000
+ }
2001
+ function CoreCollection({ model, label, section = "Cole\xE7\xE3o", mode = "dynamic", endpoint, columns, form, importExport, list, relations, detailModal, cellRenderers }) {
1916
2002
  const needsSchema = mode !== "full" || !columns || !form || !endpoint;
1917
2003
  const schemaQuery = useModelSchema(needsSchema ? model : "");
1918
2004
  const schema = schemaQuery.data;
1919
2005
  const basePath = endpoint ?? schema?.basePath ?? `/${model.toLowerCase()}s`;
1920
2006
  const resolvedColumns = (list?.columns?.length ? list.columns.map((column) => normalizeColumn(column, schema)) : columns) ?? (schema ? columnsFromMeta(schema.fields) : []);
1921
- const resolvedForm = form ?? (schema ? formFieldsFromMeta(schema.fields) : []);
2007
+ const metadataForm = schema ? formFieldsFromMeta(schema.fields) : [];
2008
+ const resolvedForm = mode === "full" && form?.length ? form : mergeFormFields(metadataForm, form);
1922
2009
  const resource = useOonResource(basePath);
1923
2010
  const queryClient = useQueryClient3();
1924
2011
  const [formState, setFormState] = useState10({ open: false });
@@ -1958,6 +2045,7 @@ function CoreCollection({ model, label, section = "Cole\xE7\xE3o", mode = "dynam
1958
2045
  resourceKey: basePath,
1959
2046
  columns: resolvedColumns,
1960
2047
  filters: list?.filters,
2048
+ cellRenderers,
1961
2049
  onCreate: () => {
1962
2050
  setFormError(null);
1963
2051
  if (detailModal?.enabled) setDetailState({ open: true });
@@ -2391,7 +2479,7 @@ function viewLabel(view) {
2391
2479
  if (view.type === "dashboard") return "Dashboard";
2392
2480
  return view.model;
2393
2481
  }
2394
- function renderView(view) {
2482
+ function renderView(view, registry) {
2395
2483
  switch (view.type) {
2396
2484
  case "collection": {
2397
2485
  const v = view;
@@ -2407,7 +2495,8 @@ function renderView(view) {
2407
2495
  importExport: v.importExport,
2408
2496
  list: v.list,
2409
2497
  relations: v.relations,
2410
- detailModal: v.detailModal
2498
+ detailModal: v.detailModal,
2499
+ cellRenderers: registry?.cellRenderers
2411
2500
  }
2412
2501
  );
2413
2502
  }
@@ -2435,10 +2524,10 @@ function renderView(view) {
2435
2524
  return null;
2436
2525
  }
2437
2526
  }
2438
- function viewToRoute(view) {
2527
+ function viewToRoute(view, registry) {
2439
2528
  return {
2440
2529
  path: viewPath(view),
2441
- element: renderView(view),
2530
+ element: renderView(view, registry),
2442
2531
  permissions: requiredAccess2(view)
2443
2532
  };
2444
2533
  }
@@ -2463,6 +2552,7 @@ function resolveRegistry(config) {
2463
2552
  const routes = [];
2464
2553
  const menu = [];
2465
2554
  const seenPaths = /* @__PURE__ */ new Set();
2555
+ const componentRegistry = config.registry ?? config.ui?.registry;
2466
2556
  const pushRoute = (route) => {
2467
2557
  if (seenPaths.has(route.path)) return;
2468
2558
  seenPaths.add(route.path);
@@ -2472,7 +2562,7 @@ function resolveRegistry(config) {
2472
2562
  for (const route of mod.routes ?? []) pushRoute({ ...route, permissions: requiredAccess2(route) });
2473
2563
  for (const item of mod.menu ?? []) menu.push({ ...item, permissions: requiredAccess2(item) });
2474
2564
  for (const view of mod.views ?? []) {
2475
- pushRoute(viewToRoute(view));
2565
+ pushRoute(viewToRoute(view, componentRegistry));
2476
2566
  menu.push(viewToMenu(view));
2477
2567
  }
2478
2568
  }
@@ -2481,11 +2571,11 @@ function resolveRegistry(config) {
2481
2571
  const includeManual = navMode === "manual" || navMode === "mixed";
2482
2572
  if (includeAuto) {
2483
2573
  for (const view of config.ui?.views ?? []) {
2484
- pushRoute(viewToRoute(view));
2574
+ pushRoute(viewToRoute(view, componentRegistry));
2485
2575
  menu.push(viewToMenu(view));
2486
2576
  }
2487
2577
  for (const page of config.ui?.pages ?? []) {
2488
- pushRoute(pageToRoute(page, config.registry ?? config.ui?.registry));
2578
+ pushRoute(pageToRoute(page, componentRegistry));
2489
2579
  const item = pageToMenu(page);
2490
2580
  if (item) menu.push(item);
2491
2581
  }
@@ -3438,7 +3528,7 @@ function localLoginUrl() {
3438
3528
  return `/login?redirect=${encodeURIComponent(current)}`;
3439
3529
  }
3440
3530
  function OonApp({ config }) {
3441
- const { http, storage, router } = useMemo6(() => {
3531
+ const { http, storage, router } = useMemo7(() => {
3442
3532
  const storage2 = createTokenStorage(config.app.id);
3443
3533
  const externalLoginUrl = config.auth?.loginUrl ?? config.api.meusAppsUrl;
3444
3534
  const http2 = createRestClient({
@@ -3533,6 +3623,7 @@ function manifestToConfig(manifest, runtime) {
3533
3623
  path: c.path,
3534
3624
  label: c.label,
3535
3625
  section: c.section,
3626
+ form: c.form,
3536
3627
  list: c.list,
3537
3628
  relations: c.relations,
3538
3629
  detailModal: c.detailModal