@oondemand/oon-core-front 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1337 @@
1
+ // src/start.tsx
2
+ import { StrictMode } from "react";
3
+ import { createRoot } from "react-dom/client";
4
+
5
+ // src/app/OonApp.tsx
6
+ import { useMemo as useMemo3 } from "react";
7
+ import { RouterProvider } from "react-router-dom";
8
+
9
+ // src/api/createRestClient.ts
10
+ import axios2 from "axios";
11
+
12
+ // src/api/errorNormalizer.ts
13
+ import axios from "axios";
14
+ function normalizeError(err) {
15
+ if (axios.isAxiosError(err)) {
16
+ const status = err.response?.status;
17
+ const data = err.response?.data;
18
+ const envelope = data?.error;
19
+ return {
20
+ code: envelope?.code || codeFromStatus(status),
21
+ message: envelope?.message || data?.message || err.message || "Erro inesperado.",
22
+ status,
23
+ details: envelope?.details,
24
+ requestId: envelope?.requestId || err.response?.headers?.["x-request-id"]
25
+ };
26
+ }
27
+ if (err instanceof Error) {
28
+ return { code: "UNKNOWN", message: err.message };
29
+ }
30
+ return { code: "UNKNOWN", message: "Erro inesperado." };
31
+ }
32
+ function codeFromStatus(status) {
33
+ switch (status) {
34
+ case 400:
35
+ return "VALIDATION_ERROR";
36
+ case 401:
37
+ return "UNAUTHORIZED";
38
+ case 403:
39
+ return "FORBIDDEN";
40
+ case 404:
41
+ return "NOT_FOUND";
42
+ case 409:
43
+ return "CONFLICT";
44
+ default:
45
+ return status && status >= 500 ? "SERVER_ERROR" : "REQUEST_ERROR";
46
+ }
47
+ }
48
+
49
+ // src/api/createRestClient.ts
50
+ function createRestClient(deps) {
51
+ const { api, getToken, onUnauthorized, appId } = deps;
52
+ const prefix = api.versionPrefix ?? "";
53
+ const instance = axios2.create({
54
+ baseURL: `${api.baseUrl.replace(/\/$/, "")}${prefix}`,
55
+ timeout: api.timeout ?? 3e4
56
+ });
57
+ instance.interceptors.request.use((config) => {
58
+ const token = getToken();
59
+ if (token) config.headers.Authorization = `Bearer ${token}`;
60
+ if (appId) config.headers["x-oon-app"] = appId;
61
+ return config;
62
+ });
63
+ instance.interceptors.response.use(
64
+ (res) => res,
65
+ (error) => {
66
+ const normalized = normalizeError(error);
67
+ if (normalized.status === 401) onUnauthorized?.();
68
+ return Promise.reject(normalized);
69
+ }
70
+ );
71
+ return instance;
72
+ }
73
+ function auditHeaders(audit) {
74
+ if (!audit) return {};
75
+ const headers = {};
76
+ if (audit.origem) headers["x-oon-origin"] = audit.origem;
77
+ if (audit.module) headers["x-oon-module"] = audit.module;
78
+ if (audit.entity) headers["x-oon-entity"] = audit.entity;
79
+ return headers;
80
+ }
81
+
82
+ // src/security/tokenStorage.ts
83
+ var STORAGE_KEY = "oon.auth.token";
84
+ function createTokenStorage(namespace) {
85
+ const key = namespace ? `${STORAGE_KEY}.${namespace}` : STORAGE_KEY;
86
+ return {
87
+ get() {
88
+ try {
89
+ return window.localStorage.getItem(key);
90
+ } catch {
91
+ return null;
92
+ }
93
+ },
94
+ set(token) {
95
+ try {
96
+ window.localStorage.setItem(key, token);
97
+ } catch {
98
+ }
99
+ },
100
+ clear() {
101
+ try {
102
+ window.localStorage.removeItem(key);
103
+ } catch {
104
+ }
105
+ }
106
+ };
107
+ }
108
+ function captureTokenFromUrl(tokenParam = "code") {
109
+ try {
110
+ const url = new URL(window.location.href);
111
+ const token = url.searchParams.get(tokenParam);
112
+ if (!token) return null;
113
+ url.searchParams.delete(tokenParam);
114
+ window.history.replaceState({}, document.title, url.pathname + url.search + url.hash);
115
+ return token;
116
+ } catch {
117
+ return null;
118
+ }
119
+ }
120
+
121
+ // src/app/OonProviders.tsx
122
+ import { useMemo as useMemo2 } from "react";
123
+ import { ChakraProvider, defaultSystem } from "@chakra-ui/react";
124
+ import { QueryClientProvider } from "@tanstack/react-query";
125
+
126
+ // src/config/queryClient.ts
127
+ import { QueryClient } from "@tanstack/react-query";
128
+ function createOonQueryClient() {
129
+ return new QueryClient({
130
+ defaultOptions: {
131
+ queries: {
132
+ retry: 1,
133
+ refetchOnWindowFocus: false,
134
+ staleTime: 30 * 1e3
135
+ },
136
+ mutations: {
137
+ retry: 0
138
+ }
139
+ }
140
+ });
141
+ }
142
+
143
+ // src/api/ApiProvider.tsx
144
+ import { createContext, useContext, useMemo } from "react";
145
+ import { useQuery } from "@tanstack/react-query";
146
+
147
+ // src/api/coreClient.ts
148
+ function createCoreClient(http) {
149
+ return {
150
+ async metadata() {
151
+ const { data } = await http.get("/core/metadata");
152
+ return data;
153
+ },
154
+ async models() {
155
+ const { data } = await http.get("/core/models");
156
+ return data.results;
157
+ },
158
+ async model(name) {
159
+ const { data } = await http.get(`/core/models/${name}`);
160
+ return data;
161
+ },
162
+ async permissions() {
163
+ const { data } = await http.get(
164
+ "/core/permissions"
165
+ );
166
+ return data.results;
167
+ },
168
+ async menus() {
169
+ const { data } = await http.get("/core/menus");
170
+ return data.results;
171
+ }
172
+ };
173
+ }
174
+
175
+ // src/api/resourceClient.ts
176
+ var META_KEYS = /* @__PURE__ */ new Set(["pagination", "results", "message", "requestId"]);
177
+ function unwrap(data) {
178
+ for (const [key, value] of Object.entries(data)) {
179
+ if (!META_KEYS.has(key) && value && typeof value === "object") {
180
+ return value;
181
+ }
182
+ }
183
+ return data;
184
+ }
185
+ function createResourceClient(http, basePath) {
186
+ const base = `/${basePath.replace(/^\/+/, "")}`;
187
+ return {
188
+ async list(params) {
189
+ const { data } = await http.get(base, { params });
190
+ return data;
191
+ },
192
+ async get(id) {
193
+ const { data } = await http.get(`${base}/${id}`);
194
+ return unwrap(data);
195
+ },
196
+ async create(body, audit) {
197
+ const { data } = await http.post(base, body, {
198
+ headers: auditHeaders(audit ?? { origem: "form" })
199
+ });
200
+ return unwrap(data);
201
+ },
202
+ async update(id, body, audit) {
203
+ const { data } = await http.put(`${base}/${id}`, body, {
204
+ headers: auditHeaders(audit ?? { origem: "form" })
205
+ });
206
+ return unwrap(data);
207
+ },
208
+ async patch(id, body, audit) {
209
+ const { data } = await http.patch(`${base}/${id}`, body, {
210
+ headers: auditHeaders(audit ?? { origem: "datagrid" })
211
+ });
212
+ return unwrap(data);
213
+ },
214
+ async delete(id, audit) {
215
+ const { data } = await http.delete(`${base}/${id}`, {
216
+ headers: auditHeaders(audit ?? { origem: "datagrid" })
217
+ });
218
+ return unwrap(data);
219
+ },
220
+ async importMany(rows, audit) {
221
+ const { data } = await http.post(
222
+ `${base}/import`,
223
+ { rows },
224
+ { headers: auditHeaders(audit ?? { origem: "import" }) }
225
+ );
226
+ return data;
227
+ },
228
+ async exportAll(params) {
229
+ const { data } = await http.get(`${base}/export`, { params });
230
+ return Array.isArray(data) ? data : data.results;
231
+ }
232
+ };
233
+ }
234
+
235
+ // src/api/ApiProvider.tsx
236
+ import { jsx } from "react/jsx-runtime";
237
+ var ApiContext = createContext(null);
238
+ function ApiProvider({ http, children }) {
239
+ const value = useMemo(() => {
240
+ const cache = /* @__PURE__ */ new Map();
241
+ return {
242
+ http,
243
+ core: createCoreClient(http),
244
+ resource(basePath) {
245
+ let client = cache.get(basePath);
246
+ if (!client) {
247
+ client = createResourceClient(http, basePath);
248
+ cache.set(basePath, client);
249
+ }
250
+ return client;
251
+ }
252
+ };
253
+ }, [http]);
254
+ return /* @__PURE__ */ jsx(ApiContext.Provider, { value, children });
255
+ }
256
+ function useOonApi() {
257
+ const ctx = useContext(ApiContext);
258
+ if (!ctx) throw new Error("useOonApi deve ser usado dentro de <ApiProvider> (oonCoreFront.start).");
259
+ return ctx;
260
+ }
261
+ function useOonResource(basePath) {
262
+ const { resource } = useOonApi();
263
+ return useMemo(() => resource(basePath), [resource, basePath]);
264
+ }
265
+ function useCoreMetadata() {
266
+ const { core } = useOonApi();
267
+ return useQuery({
268
+ queryKey: ["oon", "core", "metadata"],
269
+ queryFn: () => core.metadata(),
270
+ staleTime: 5 * 60 * 1e3
271
+ });
272
+ }
273
+ function useModelSchema(name) {
274
+ const { core } = useOonApi();
275
+ return useQuery({
276
+ queryKey: ["oon", "core", "model", name],
277
+ queryFn: () => core.model(name),
278
+ staleTime: 5 * 60 * 1e3,
279
+ enabled: !!name
280
+ });
281
+ }
282
+
283
+ // src/security/AuthProvider.tsx
284
+ import { createContext as createContext2, useContext as useContext2, useEffect, useState, useCallback } from "react";
285
+ import { jsx as jsx2 } from "react/jsx-runtime";
286
+ var AuthContext = createContext2(null);
287
+ function AuthProvider({ http, storage, auth, loginUrl, children }) {
288
+ const tokenParam = auth?.tokenParam ?? "code";
289
+ const [user, setUser] = useState(null);
290
+ const [status, setStatus] = useState("loading");
291
+ const redirectToLogin = useCallback(() => {
292
+ if (loginUrl) {
293
+ window.location.href = loginUrl;
294
+ } else {
295
+ setStatus("unauthenticated");
296
+ }
297
+ }, [loginUrl]);
298
+ const logout = useCallback(() => {
299
+ storage.clear();
300
+ setUser(null);
301
+ redirectToLogin();
302
+ }, [storage, redirectToLogin]);
303
+ useEffect(() => {
304
+ let active = true;
305
+ const incoming = captureTokenFromUrl(tokenParam);
306
+ if (incoming) storage.set(incoming);
307
+ const token = storage.get();
308
+ if (!token) {
309
+ redirectToLogin();
310
+ return;
311
+ }
312
+ http.get("/auth/validar-token").then((res) => {
313
+ if (!active) return;
314
+ setUser(res.data.usuario);
315
+ setStatus("authenticated");
316
+ }).catch(() => {
317
+ if (!active) return;
318
+ storage.clear();
319
+ redirectToLogin();
320
+ });
321
+ return () => {
322
+ active = false;
323
+ };
324
+ }, [http, storage, tokenParam, redirectToLogin]);
325
+ const roles = user?.tipo ? [user.tipo] : [];
326
+ const hasPermission = useCallback(
327
+ (permission) => {
328
+ if (!permission) return true;
329
+ const required = Array.isArray(permission) ? permission : [permission];
330
+ if (required.length === 0) return true;
331
+ if (roles.includes("admin") || roles.includes("master")) return true;
332
+ return required.some((p) => roles.includes(p) || roles.some((r) => p.startsWith(`${r}.`)));
333
+ },
334
+ [roles]
335
+ );
336
+ const value = {
337
+ user,
338
+ roles,
339
+ status,
340
+ isAuthenticated: status === "authenticated",
341
+ hasPermission,
342
+ logout
343
+ };
344
+ return /* @__PURE__ */ jsx2(AuthContext.Provider, { value, children });
345
+ }
346
+ function useOonAuth() {
347
+ const ctx = useContext2(AuthContext);
348
+ if (!ctx) throw new Error("useOonAuth deve ser usado dentro de <AuthProvider> (oonCoreFront.start).");
349
+ return ctx;
350
+ }
351
+
352
+ // src/app/OonProviders.tsx
353
+ import { jsx as jsx3 } from "react/jsx-runtime";
354
+ function OonProviders({ config, http, storage, children }) {
355
+ const queryClient = useMemo2(() => createOonQueryClient(), []);
356
+ const loginUrl = config.auth?.loginUrl ?? config.api.meusAppsUrl;
357
+ return /* @__PURE__ */ jsx3(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx3(ChakraProvider, { value: defaultSystem, children: /* @__PURE__ */ jsx3(ApiProvider, { http, children: /* @__PURE__ */ jsx3(AuthProvider, { http, storage, auth: config.auth, loginUrl, children }) }) }) });
358
+ }
359
+
360
+ // src/components/CoreCollection.tsx
361
+ import { useState as useState4 } from "react";
362
+ import { Box as Box4, Heading as Heading3, Stack as Stack2 } from "@chakra-ui/react";
363
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
364
+
365
+ // src/components/internal/fieldUtils.ts
366
+ var HIDDEN_FIELDS = /* @__PURE__ */ new Set(["__v", "_id", "createdAt", "updatedAt", "criadoEm", "atualizadoEm"]);
367
+ function isEditableField(meta) {
368
+ return !HIDDEN_FIELDS.has(meta.name) && meta.kind !== "raw";
369
+ }
370
+ function fieldLabel(meta) {
371
+ if (meta.label) return meta.label;
372
+ return meta.name.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (c) => c.toUpperCase());
373
+ }
374
+ function columnsFromMeta(fields) {
375
+ return fields.filter(isEditableField).slice(0, 8).map((f) => ({ field: f.name, label: fieldLabel(f), kind: f.kind, sortable: true }));
376
+ }
377
+ function formFieldsFromMeta(fields) {
378
+ return fields.filter(isEditableField).map((f) => ({
379
+ field: f.name,
380
+ label: fieldLabel(f),
381
+ kind: f.kind,
382
+ options: f.options
383
+ }));
384
+ }
385
+ function formatCell(value, kind) {
386
+ if (value == null || value === "") return "\u2014";
387
+ switch (kind) {
388
+ case "boolean":
389
+ return value ? "Sim" : "N\xE3o";
390
+ case "date":
391
+ try {
392
+ return new Date(value).toLocaleDateString("pt-BR");
393
+ } catch {
394
+ return String(value);
395
+ }
396
+ case "currency":
397
+ case "currencyConverted":
398
+ return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(Number(value));
399
+ case "ref":
400
+ if (value && typeof value === "object") {
401
+ const obj = value;
402
+ return String(obj.nome ?? obj.label ?? obj.descricao ?? obj._id ?? "\u2014");
403
+ }
404
+ return String(value);
405
+ default:
406
+ return String(value);
407
+ }
408
+ }
409
+
410
+ // src/components/internal/DataGrid.tsx
411
+ import { useState as useState2 } from "react";
412
+ import { Box as Box2, Button, Flex as Flex2, Input, Spacer, Table, Text as Text3 } from "@chakra-ui/react";
413
+ import { keepPreviousData, useQuery as useQuery2 } from "@tanstack/react-query";
414
+
415
+ // src/shell/LoadingScreen.tsx
416
+ import { Flex, Spinner, Text } from "@chakra-ui/react";
417
+ import { jsx as jsx4, jsxs } from "react/jsx-runtime";
418
+ function LoadingScreen({ message }) {
419
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", align: "center", justify: "center", minH: "60vh", gap: 4, children: [
420
+ /* @__PURE__ */ jsx4(Spinner, { size: "lg" }),
421
+ message ? /* @__PURE__ */ jsx4(Text, { color: "gray.500", fontSize: "sm", children: message }) : null
422
+ ] });
423
+ }
424
+
425
+ // src/shell/ErrorState.tsx
426
+ import { Box, Heading, Text as Text2 } from "@chakra-ui/react";
427
+ import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
428
+ function ErrorState({
429
+ error,
430
+ title = "N\xE3o foi poss\xEDvel carregar"
431
+ }) {
432
+ return /* @__PURE__ */ jsxs2(Box, { borderWidth: "1px", borderColor: "red.200", bg: "red.50", borderRadius: "md", p: 4, children: [
433
+ /* @__PURE__ */ jsx5(Heading, { size: "sm", color: "red.700", children: title }),
434
+ /* @__PURE__ */ jsx5(Text2, { mt: 1, fontSize: "sm", color: "red.600", children: error?.message ?? "Erro inesperado." }),
435
+ error?.requestId ? /* @__PURE__ */ jsxs2(Text2, { mt: 2, fontSize: "xs", color: "red.400", children: [
436
+ "requestId: ",
437
+ error.requestId
438
+ ] }) : null
439
+ ] });
440
+ }
441
+
442
+ // src/components/internal/DataGrid.tsx
443
+ import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
444
+ var PAGE_SIZE = 20;
445
+ function DataGrid({
446
+ resource,
447
+ resourceKey,
448
+ columns,
449
+ search = true,
450
+ onCreate,
451
+ onEdit,
452
+ onDelete,
453
+ toolbarExtra
454
+ }) {
455
+ const [pageIndex, setPageIndex] = useState2(0);
456
+ const [searchTerm, setSearchTerm] = useState2("");
457
+ const [sort, setSort] = useState2(void 0);
458
+ const query = useQuery2({
459
+ queryKey: ["oon", "list", resourceKey, { pageIndex, searchTerm, sort }],
460
+ queryFn: () => resource.list({ pageIndex, pageSize: PAGE_SIZE, searchTerm: searchTerm || void 0, sort }),
461
+ placeholderData: keepPreviousData
462
+ });
463
+ const toggleSort = (field) => {
464
+ setPageIndex(0);
465
+ setSort((prev) => {
466
+ if (prev === `${field}.asc`) return `${field}.desc`;
467
+ if (prev === `${field}.desc`) return void 0;
468
+ return `${field}.asc`;
469
+ });
470
+ };
471
+ const pagination = query.data?.pagination;
472
+ const rows = query.data?.results ?? [];
473
+ return /* @__PURE__ */ jsxs3(Box2, { children: [
474
+ /* @__PURE__ */ jsxs3(Flex2, { mb: 4, gap: 3, align: "center", children: [
475
+ search ? /* @__PURE__ */ jsx6(
476
+ Input,
477
+ {
478
+ size: "sm",
479
+ maxW: "320px",
480
+ placeholder: "Buscar...",
481
+ value: searchTerm,
482
+ onChange: (e) => {
483
+ setPageIndex(0);
484
+ setSearchTerm(e.target.value);
485
+ }
486
+ }
487
+ ) : null,
488
+ /* @__PURE__ */ jsx6(Spacer, {}),
489
+ toolbarExtra,
490
+ onCreate ? /* @__PURE__ */ jsx6(Button, { size: "sm", colorPalette: "blue", onClick: onCreate, children: "Novo" }) : null
491
+ ] }),
492
+ query.isLoading ? /* @__PURE__ */ jsx6(LoadingScreen, {}) : query.isError ? /* @__PURE__ */ jsx6(ErrorState, { error: query.error }) : /* @__PURE__ */ jsx6(Box2, { borderWidth: "1px", borderColor: "gray.200", borderRadius: "md", overflowX: "auto", bg: "white", children: /* @__PURE__ */ jsxs3(Table.Root, { size: "sm", interactive: true, children: [
493
+ /* @__PURE__ */ jsx6(Table.Header, { children: /* @__PURE__ */ jsxs3(Table.Row, { children: [
494
+ columns.map((col) => /* @__PURE__ */ jsxs3(
495
+ Table.ColumnHeader,
496
+ {
497
+ cursor: col.sortable ? "pointer" : void 0,
498
+ onClick: col.sortable ? () => toggleSort(col.field) : void 0,
499
+ children: [
500
+ col.label ?? col.field,
501
+ sort?.startsWith(`${col.field}.`) ? sort.endsWith(".asc") ? " \u25B2" : " \u25BC" : ""
502
+ ]
503
+ },
504
+ col.field
505
+ )),
506
+ onEdit || onDelete ? /* @__PURE__ */ jsx6(Table.ColumnHeader, { textAlign: "end", children: "A\xE7\xF5es" }) : null
507
+ ] }) }),
508
+ /* @__PURE__ */ jsx6(Table.Body, { children: rows.length === 0 ? /* @__PURE__ */ jsx6(Table.Row, { children: /* @__PURE__ */ jsx6(Table.Cell, { colSpan: columns.length + 1, children: /* @__PURE__ */ jsx6(Text3, { color: "gray.400", py: 6, textAlign: "center", children: "Nenhum registro encontrado." }) }) }) : rows.map((row, idx) => /* @__PURE__ */ jsxs3(Table.Row, { children: [
509
+ columns.map((col) => /* @__PURE__ */ jsx6(Table.Cell, { children: formatCell(row[col.field], col.kind) }, col.field)),
510
+ onEdit || onDelete ? /* @__PURE__ */ jsx6(Table.Cell, { textAlign: "end", children: /* @__PURE__ */ jsxs3(Flex2, { gap: 2, justify: "end", children: [
511
+ onEdit ? /* @__PURE__ */ jsx6(Button, { size: "xs", variant: "ghost", onClick: () => onEdit(row), children: "Editar" }) : null,
512
+ onDelete ? /* @__PURE__ */ jsx6(Button, { size: "xs", variant: "ghost", colorPalette: "red", onClick: () => onDelete(row), children: "Excluir" }) : null
513
+ ] }) }) : null
514
+ ] }, row._id ?? idx)) })
515
+ ] }) }),
516
+ pagination ? /* @__PURE__ */ jsxs3(Flex2, { mt: 4, align: "center", gap: 3, children: [
517
+ /* @__PURE__ */ jsxs3(Text3, { fontSize: "sm", color: "gray.500", children: [
518
+ pagination.totalItems,
519
+ " registro(s) \xB7 p\xE1gina ",
520
+ pagination.currentPage,
521
+ " de ",
522
+ pagination.totalPages
523
+ ] }),
524
+ /* @__PURE__ */ jsx6(Spacer, {}),
525
+ /* @__PURE__ */ jsx6(Button, { size: "xs", variant: "outline", disabled: pageIndex <= 0, onClick: () => setPageIndex((p) => p - 1), children: "Anterior" }),
526
+ /* @__PURE__ */ jsx6(
527
+ Button,
528
+ {
529
+ size: "xs",
530
+ variant: "outline",
531
+ disabled: pagination.currentPage >= pagination.totalPages,
532
+ onClick: () => setPageIndex((p) => p + 1),
533
+ children: "Pr\xF3xima"
534
+ }
535
+ )
536
+ ] }) : null
537
+ ] });
538
+ }
539
+
540
+ // src/components/internal/DynamicForm.tsx
541
+ import { useState as useState3 } from "react";
542
+ import { Box as Box3, Button as Button2, Flex as Flex3, Heading as Heading2, Input as Input2, Stack, Text as Text4, NativeSelect } from "@chakra-ui/react";
543
+ import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
544
+ function coerce(value, kind) {
545
+ if (value === "") return void 0;
546
+ if (kind === "number" || kind === "currency" || kind === "currencyConverted") return Number(value);
547
+ if (kind === "boolean") return value === "true";
548
+ return value;
549
+ }
550
+ function toInputValue(value, kind) {
551
+ if (value == null) return "";
552
+ if (kind === "date" && value) {
553
+ try {
554
+ return new Date(value).toISOString().slice(0, 10);
555
+ } catch {
556
+ return "";
557
+ }
558
+ }
559
+ if (typeof value === "object") {
560
+ const obj = value;
561
+ return String(obj._id ?? "");
562
+ }
563
+ return String(value);
564
+ }
565
+ function DynamicForm({ title, fields, initialValues, submitting, error, onSubmit, onCancel }) {
566
+ const [values, setValues] = useState3(() => {
567
+ const init = {};
568
+ for (const f of fields) init[f.field] = toInputValue(initialValues?.[f.field], f.kind);
569
+ return init;
570
+ });
571
+ const setField = (name, value) => setValues((prev) => ({ ...prev, [name]: value }));
572
+ const handleSubmit = (e) => {
573
+ e.preventDefault();
574
+ const payload = {};
575
+ for (const f of fields) {
576
+ const coerced = coerce(values[f.field] ?? "", f.kind);
577
+ if (coerced !== void 0) payload[f.field] = coerced;
578
+ }
579
+ onSubmit(payload);
580
+ };
581
+ return /* @__PURE__ */ jsxs4(Box3, { borderWidth: "1px", borderColor: "gray.200", borderRadius: "md", bg: "white", p: 6, maxW: "640px", children: [
582
+ /* @__PURE__ */ jsx7(Heading2, { size: "md", mb: 4, children: title }),
583
+ /* @__PURE__ */ jsx7("form", { onSubmit: handleSubmit, children: /* @__PURE__ */ jsxs4(Stack, { gap: 4, children: [
584
+ fields.map((f) => {
585
+ const label = fieldLabel({ name: f.field, label: f.label });
586
+ if (f.kind === "enum" && f.options?.length) {
587
+ return /* @__PURE__ */ jsxs4(Box3, { children: [
588
+ /* @__PURE__ */ jsx7(Text4, { fontSize: "sm", mb: 1, fontWeight: "medium", children: label }),
589
+ /* @__PURE__ */ jsxs4(NativeSelect.Root, { size: "sm", children: [
590
+ /* @__PURE__ */ jsxs4(
591
+ NativeSelect.Field,
592
+ {
593
+ value: values[f.field] ?? "",
594
+ onChange: (e) => setField(f.field, e.currentTarget.value),
595
+ children: [
596
+ /* @__PURE__ */ jsx7("option", { value: "", children: "\u2014" }),
597
+ f.options.map((opt) => /* @__PURE__ */ jsx7("option", { value: opt, children: opt }, opt))
598
+ ]
599
+ }
600
+ ),
601
+ /* @__PURE__ */ jsx7(NativeSelect.Indicator, {})
602
+ ] })
603
+ ] }, f.field);
604
+ }
605
+ if (f.kind === "boolean") {
606
+ return /* @__PURE__ */ jsxs4(Box3, { children: [
607
+ /* @__PURE__ */ jsx7(Text4, { fontSize: "sm", mb: 1, fontWeight: "medium", children: label }),
608
+ /* @__PURE__ */ jsxs4(NativeSelect.Root, { size: "sm", children: [
609
+ /* @__PURE__ */ jsxs4(
610
+ NativeSelect.Field,
611
+ {
612
+ value: values[f.field] ?? "",
613
+ onChange: (e) => setField(f.field, e.currentTarget.value),
614
+ children: [
615
+ /* @__PURE__ */ jsx7("option", { value: "", children: "\u2014" }),
616
+ /* @__PURE__ */ jsx7("option", { value: "true", children: "Sim" }),
617
+ /* @__PURE__ */ jsx7("option", { value: "false", children: "N\xE3o" })
618
+ ]
619
+ }
620
+ ),
621
+ /* @__PURE__ */ jsx7(NativeSelect.Indicator, {})
622
+ ] })
623
+ ] }, f.field);
624
+ }
625
+ const inputType = f.kind === "date" ? "date" : f.kind === "number" || f.kind === "currency" || f.kind === "currencyConverted" ? "number" : "text";
626
+ return /* @__PURE__ */ jsxs4(Box3, { children: [
627
+ /* @__PURE__ */ jsxs4(Text4, { fontSize: "sm", mb: 1, fontWeight: "medium", children: [
628
+ label,
629
+ f.required ? " *" : ""
630
+ ] }),
631
+ /* @__PURE__ */ jsx7(
632
+ Input2,
633
+ {
634
+ size: "sm",
635
+ type: inputType,
636
+ required: f.required,
637
+ value: values[f.field] ?? "",
638
+ onChange: (e) => setField(f.field, e.currentTarget.value)
639
+ }
640
+ )
641
+ ] }, f.field);
642
+ }),
643
+ error ? /* @__PURE__ */ jsx7(Text4, { fontSize: "sm", color: "red.600", children: error }) : null,
644
+ /* @__PURE__ */ jsxs4(Flex3, { gap: 3, justify: "end", mt: 2, children: [
645
+ /* @__PURE__ */ jsx7(Button2, { size: "sm", variant: "ghost", type: "button", onClick: onCancel, children: "Cancelar" }),
646
+ /* @__PURE__ */ jsx7(Button2, { size: "sm", colorPalette: "blue", type: "submit", loading: submitting, children: "Salvar" })
647
+ ] })
648
+ ] }) })
649
+ ] });
650
+ }
651
+
652
+ // src/components/CoreCollection.tsx
653
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
654
+ function CoreCollection({ model, label, mode = "dynamic", endpoint, columns, form, importExport }) {
655
+ const needsSchema = mode !== "full" || !columns || !form || !endpoint;
656
+ const schemaQuery = useModelSchema(needsSchema ? model : "");
657
+ const schema = schemaQuery.data;
658
+ const basePath = endpoint ?? schema?.basePath ?? `/${model.toLowerCase()}s`;
659
+ const resolvedColumns = columns ?? (schema ? columnsFromMeta(schema.fields) : []);
660
+ const resolvedForm = form ?? (schema ? formFieldsFromMeta(schema.fields) : []);
661
+ const resource = useOonResource(basePath);
662
+ const queryClient = useQueryClient();
663
+ const [formState, setFormState] = useState4({ open: false });
664
+ const [formError, setFormError] = useState4(null);
665
+ const invalidate = () => queryClient.invalidateQueries({ queryKey: ["oon", "list", basePath] });
666
+ const saveMutation = useMutation({
667
+ mutationFn: (values) => {
668
+ const editingRow = formState.open ? formState.row : void 0;
669
+ const id = editingRow?._id;
670
+ return id ? resource.update(id, values) : resource.create(values);
671
+ },
672
+ onSuccess: () => {
673
+ setFormState({ open: false });
674
+ setFormError(null);
675
+ invalidate();
676
+ },
677
+ onError: (err) => setFormError(err.message)
678
+ });
679
+ const deleteMutation = useMutation({
680
+ mutationFn: (row) => resource.delete(row._id),
681
+ onSuccess: invalidate
682
+ });
683
+ if (needsSchema && schemaQuery.isLoading) return /* @__PURE__ */ jsx8(LoadingScreen, {});
684
+ if (needsSchema && schemaQuery.isError) return /* @__PURE__ */ jsx8(ErrorState, { error: schemaQuery.error });
685
+ const title = label ?? schema?.name ?? model;
686
+ return /* @__PURE__ */ jsxs5(Stack2, { gap: 6, children: [
687
+ /* @__PURE__ */ jsx8(Heading3, { size: "lg", children: title }),
688
+ formState.open ? /* @__PURE__ */ jsx8(
689
+ DynamicForm,
690
+ {
691
+ title: formState.row ? `Editar ${title}` : `Novo ${title}`,
692
+ fields: resolvedForm,
693
+ initialValues: formState.row,
694
+ submitting: saveMutation.isPending,
695
+ error: formError,
696
+ onSubmit: (values) => saveMutation.mutate(values),
697
+ onCancel: () => {
698
+ setFormState({ open: false });
699
+ setFormError(null);
700
+ }
701
+ }
702
+ ) : /* @__PURE__ */ jsxs5(Box4, { children: [
703
+ /* @__PURE__ */ jsx8(
704
+ DataGrid,
705
+ {
706
+ resource,
707
+ resourceKey: basePath,
708
+ columns: resolvedColumns,
709
+ onCreate: () => setFormState({ open: true }),
710
+ onEdit: (row) => setFormState({ open: true, row }),
711
+ onDelete: (row) => {
712
+ if (window.confirm("Excluir este registro?")) deleteMutation.mutate(row);
713
+ }
714
+ }
715
+ ),
716
+ importExport ? null : null
717
+ ] })
718
+ ] });
719
+ }
720
+
721
+ // src/components/CoreDocument.tsx
722
+ import { jsx as jsx9 } from "react/jsx-runtime";
723
+ function CoreDocument({ model, label, endpoint, columns, form }) {
724
+ return /* @__PURE__ */ jsx9(
725
+ CoreCollection,
726
+ {
727
+ model,
728
+ label,
729
+ mode: columns && form ? "full" : "dynamic",
730
+ endpoint,
731
+ columns,
732
+ form
733
+ }
734
+ );
735
+ }
736
+
737
+ // src/components/CorePipeline.tsx
738
+ import { Box as Box5, Flex as Flex4, Heading as Heading4, Stack as Stack3, Text as Text5 } from "@chakra-ui/react";
739
+ import { useQuery as useQuery3 } from "@tanstack/react-query";
740
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
741
+ function CorePipeline({ model, label, endpoint, stageField = "status", stages, titleField }) {
742
+ const schemaQuery = useModelSchema(model);
743
+ const schema = schemaQuery.data;
744
+ const basePath = endpoint ?? schema?.basePath ?? `/${model.toLowerCase()}s`;
745
+ const resource = useOonResource(basePath);
746
+ const listQuery = useQuery3({
747
+ queryKey: ["oon", "pipeline", basePath],
748
+ queryFn: () => resource.list({ pageSize: 200 }),
749
+ enabled: !!schema
750
+ });
751
+ if (schemaQuery.isLoading || listQuery.isLoading) return /* @__PURE__ */ jsx10(LoadingScreen, {});
752
+ if (schemaQuery.isError) return /* @__PURE__ */ jsx10(ErrorState, { error: schemaQuery.error });
753
+ if (listQuery.isError) return /* @__PURE__ */ jsx10(ErrorState, { error: listQuery.error });
754
+ const stageFieldMeta = schema?.fields.find((f) => f.name === stageField);
755
+ const resolvedStages = stages ?? (stageFieldMeta?.options ?? []).map((opt) => ({ id: opt, label: opt })) ?? [];
756
+ const title = titleField ?? schema?.fields.find((f) => f.kind === "string")?.name ?? schema?.searchable[0] ?? "_id";
757
+ const rows = listQuery.data?.results ?? [];
758
+ const byStage = /* @__PURE__ */ new Map();
759
+ for (const stage of resolvedStages) byStage.set(stage.id, []);
760
+ for (const row of rows) {
761
+ const key = String(row[stageField] ?? "");
762
+ if (!byStage.has(key)) byStage.set(key, []);
763
+ byStage.get(key).push(row);
764
+ }
765
+ return /* @__PURE__ */ jsxs6(Stack3, { gap: 6, children: [
766
+ /* @__PURE__ */ jsx10(Heading4, { size: "lg", children: label ?? schema?.name ?? model }),
767
+ /* @__PURE__ */ jsx10(Flex4, { gap: 4, align: "stretch", overflowX: "auto", pb: 2, children: Array.from(byStage.entries()).map(([stageId, items]) => {
768
+ const stageLabel = resolvedStages.find((s) => s.id === stageId)?.label ?? (stageId || "\u2014");
769
+ return /* @__PURE__ */ jsxs6(Box5, { minW: "280px", bg: "gray.100", borderRadius: "md", p: 3, children: [
770
+ /* @__PURE__ */ jsxs6(Flex4, { align: "center", justify: "space-between", mb: 3, children: [
771
+ /* @__PURE__ */ jsx10(Text5, { fontWeight: "semibold", fontSize: "sm", children: stageLabel }),
772
+ /* @__PURE__ */ jsx10(Text5, { fontSize: "xs", color: "gray.500", children: items.length })
773
+ ] }),
774
+ /* @__PURE__ */ jsx10(Stack3, { gap: 2, children: items.map((row, idx) => /* @__PURE__ */ jsx10(Box5, { bg: "white", borderRadius: "md", p: 3, borderWidth: "1px", borderColor: "gray.200", children: /* @__PURE__ */ jsx10(Text5, { fontSize: "sm", fontWeight: "medium", children: formatCell(row[title], "string") }) }, row._id ?? idx)) })
775
+ ] }, stageId);
776
+ }) })
777
+ ] });
778
+ }
779
+
780
+ // src/components/CoreDashboard.tsx
781
+ import { Box as Box6, Heading as Heading5, SimpleGrid, Stack as Stack4, Text as Text6 } from "@chakra-ui/react";
782
+ import { useQueries } from "@tanstack/react-query";
783
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
784
+ function CoreDashboard({ label = "Dashboard", widgets = [] }) {
785
+ const { resource } = useOonApi();
786
+ const queries = useQueries({
787
+ queries: widgets.filter((w) => w.kind !== "custom" && w.model).map((w) => ({
788
+ queryKey: ["oon", "widget", w.id],
789
+ queryFn: async () => {
790
+ const r = await resource(`/${w.model.toLowerCase()}s`).list({ pageSize: w.kind === "sum" ? 200 : 1 });
791
+ if (w.kind === "sum" && w.field) {
792
+ return r.results.reduce((acc, row) => acc + Number(row[w.field] ?? 0), 0);
793
+ }
794
+ return r.pagination.totalItems;
795
+ },
796
+ staleTime: 60 * 1e3
797
+ }))
798
+ });
799
+ let queryIdx = 0;
800
+ return /* @__PURE__ */ jsxs7(Stack4, { gap: 6, children: [
801
+ /* @__PURE__ */ jsx11(Heading5, { size: "lg", children: label }),
802
+ /* @__PURE__ */ jsx11(SimpleGrid, { columns: { base: 1, md: 2, lg: 3 }, gap: 4, children: widgets.map((w) => {
803
+ if (w.kind === "custom" && w.render) {
804
+ return /* @__PURE__ */ jsx11(Box6, { borderWidth: "1px", borderColor: "gray.200", borderRadius: "md", bg: "white", p: 5, children: w.render() }, w.id);
805
+ }
806
+ const q = queries[queryIdx++];
807
+ const value = q?.isLoading ? "\u2026" : q?.isError ? "\u2014" : formatWidget(q?.data, w.kind, w.field);
808
+ return /* @__PURE__ */ jsxs7(Box6, { borderWidth: "1px", borderColor: "gray.200", borderRadius: "md", bg: "white", p: 5, children: [
809
+ /* @__PURE__ */ jsx11(Text6, { fontSize: "sm", color: "gray.500", children: w.label }),
810
+ /* @__PURE__ */ jsx11(Heading5, { size: "xl", mt: 1, children: value })
811
+ ] }, w.id);
812
+ }) })
813
+ ] });
814
+ }
815
+ function formatWidget(value, kind, field) {
816
+ if (value == null) return "\u2014";
817
+ if (kind === "sum" && field?.toLowerCase().includes("valor") || kind === "sum") {
818
+ return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(Number(value));
819
+ }
820
+ return String(value);
821
+ }
822
+
823
+ // src/app/moduleRegistry.tsx
824
+ import { jsx as jsx12 } from "react/jsx-runtime";
825
+ function slug(value) {
826
+ return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
827
+ }
828
+ function viewId(view) {
829
+ if (view.id) return view.id;
830
+ if (view.type === "dashboard") return "dashboard";
831
+ return slug(view.model);
832
+ }
833
+ function viewPath(view) {
834
+ if (view.path) return view.path;
835
+ if (view.type === "dashboard") return "/";
836
+ return `/${viewId(view)}`;
837
+ }
838
+ function viewLabel(view) {
839
+ if (view.label) return view.label;
840
+ if (view.type === "dashboard") return "Dashboard";
841
+ return view.model;
842
+ }
843
+ function renderView(view) {
844
+ switch (view.type) {
845
+ case "collection": {
846
+ const v = view;
847
+ return /* @__PURE__ */ jsx12(
848
+ CoreCollection,
849
+ {
850
+ model: v.model,
851
+ mode: v.mode,
852
+ columns: v.columns,
853
+ form: v.form,
854
+ label: viewLabel(v),
855
+ importExport: v.importExport
856
+ }
857
+ );
858
+ }
859
+ case "document": {
860
+ const v = view;
861
+ return /* @__PURE__ */ jsx12(CoreDocument, { model: v.model, label: viewLabel(v), approval: v.approval, attachments: v.attachments });
862
+ }
863
+ case "pipeline": {
864
+ const v = view;
865
+ return /* @__PURE__ */ jsx12(
866
+ CorePipeline,
867
+ {
868
+ model: v.model,
869
+ label: viewLabel(v),
870
+ stageField: v.stageField,
871
+ stages: v.stages
872
+ }
873
+ );
874
+ }
875
+ case "dashboard": {
876
+ const v = view;
877
+ return /* @__PURE__ */ jsx12(CoreDashboard, { label: viewLabel(v), widgets: v.widgets });
878
+ }
879
+ default:
880
+ return null;
881
+ }
882
+ }
883
+ function viewToRoute(view) {
884
+ return {
885
+ path: viewPath(view),
886
+ element: renderView(view),
887
+ permissions: "permissions" in view ? view.permissions : void 0
888
+ };
889
+ }
890
+ function viewToMenu(view) {
891
+ return {
892
+ label: viewLabel(view),
893
+ href: viewPath(view),
894
+ icon: view.icon,
895
+ section: view.section,
896
+ permissions: "permissions" in view ? view.permissions : void 0
897
+ };
898
+ }
899
+ function resolveRegistry(config) {
900
+ const routes = [];
901
+ const menu = [];
902
+ const seenPaths = /* @__PURE__ */ new Set();
903
+ const pushRoute = (route) => {
904
+ if (seenPaths.has(route.path)) return;
905
+ seenPaths.add(route.path);
906
+ routes.push(route);
907
+ };
908
+ for (const mod of config.modules ?? []) {
909
+ for (const route of mod.routes ?? []) pushRoute(route);
910
+ for (const item of mod.menu ?? []) menu.push(item);
911
+ for (const view of mod.views ?? []) {
912
+ pushRoute(viewToRoute(view));
913
+ menu.push(viewToMenu(view));
914
+ }
915
+ }
916
+ for (const view of config.ui?.views ?? []) {
917
+ pushRoute(viewToRoute(view));
918
+ menu.push(viewToMenu(view));
919
+ }
920
+ menu.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
921
+ return { routes, menu };
922
+ }
923
+
924
+ // src/app/createOonRouter.tsx
925
+ import { createBrowserRouter } from "react-router-dom";
926
+
927
+ // src/shell/AuthenticatedLayout.tsx
928
+ import { Box as Box8, Button as Button3, Flex as Flex6, Spacer as Spacer2, Text as Text8 } from "@chakra-ui/react";
929
+ import { Outlet } from "react-router-dom";
930
+
931
+ // src/shell/Sidebar.tsx
932
+ import { Box as Box7, Flex as Flex5, Image, Stack as Stack5, Text as Text7 } from "@chakra-ui/react";
933
+ import { NavLink } from "react-router-dom";
934
+ import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
935
+ function groupBySection(items) {
936
+ const groups = /* @__PURE__ */ new Map();
937
+ for (const item of items) {
938
+ const section = item.section ?? "";
939
+ if (!groups.has(section)) groups.set(section, []);
940
+ groups.get(section).push(item);
941
+ }
942
+ return Array.from(groups.entries());
943
+ }
944
+ function Sidebar({ app, menu }) {
945
+ const { hasPermission } = useOonAuth();
946
+ const visible = menu.filter((item) => hasPermission(item.permissions));
947
+ return /* @__PURE__ */ jsxs8(Box7, { as: "nav", w: "260px", minH: "100vh", borderRightWidth: "1px", borderColor: "gray.200", px: 4, py: 6, bg: "white", children: [
948
+ /* @__PURE__ */ jsxs8(Flex5, { align: "center", gap: 3, mb: 8, px: 2, children: [
949
+ app.logo ? /* @__PURE__ */ jsx13(Image, { src: app.logo, alt: app.name, maxH: "36px" }) : null,
950
+ /* @__PURE__ */ jsx13(Text7, { fontWeight: "bold", fontSize: "md", children: app.name })
951
+ ] }),
952
+ /* @__PURE__ */ jsx13(Stack5, { gap: 6, children: groupBySection(visible).map(([section, items]) => /* @__PURE__ */ jsxs8(Stack5, { gap: 1, children: [
953
+ section ? /* @__PURE__ */ jsx13(Text7, { fontSize: "xs", fontWeight: "semibold", color: "gray.400", textTransform: "uppercase", px: 3, mb: 1, children: section }) : null,
954
+ items.map((item) => /* @__PURE__ */ jsx13(NavLink, { to: item.href, end: item.href === "/", children: ({ isActive }) => /* @__PURE__ */ jsxs8(
955
+ Flex5,
956
+ {
957
+ align: "center",
958
+ gap: 2,
959
+ px: 3,
960
+ py: 2,
961
+ borderRadius: "md",
962
+ fontSize: "sm",
963
+ fontWeight: isActive ? "semibold" : "normal",
964
+ bg: isActive ? "blue.50" : "transparent",
965
+ color: isActive ? "blue.700" : "gray.700",
966
+ _hover: { bg: isActive ? "blue.50" : "gray.50" },
967
+ children: [
968
+ item.icon,
969
+ /* @__PURE__ */ jsx13("span", { children: item.label })
970
+ ]
971
+ }
972
+ ) }, item.href))
973
+ ] }, section || "_")) })
974
+ ] });
975
+ }
976
+
977
+ // src/shell/AuthenticatedLayout.tsx
978
+ import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
979
+ function AuthenticatedLayout({ app, menu }) {
980
+ const { user, logout } = useOonAuth();
981
+ return /* @__PURE__ */ jsxs9(Flex6, { align: "stretch", minH: "100vh", bg: "gray.50", children: [
982
+ /* @__PURE__ */ jsx14(Sidebar, { app, menu }),
983
+ /* @__PURE__ */ jsxs9(Flex6, { direction: "column", flex: "1", minW: 0, children: [
984
+ /* @__PURE__ */ jsxs9(Flex6, { as: "header", align: "center", px: 6, py: 3, borderBottomWidth: "1px", borderColor: "gray.200", bg: "white", children: [
985
+ /* @__PURE__ */ jsx14(Text8, { fontSize: "sm", color: "gray.500", children: app.title ?? app.name }),
986
+ /* @__PURE__ */ jsx14(Spacer2, {}),
987
+ user ? /* @__PURE__ */ jsxs9(Flex6, { align: "center", gap: 3, children: [
988
+ /* @__PURE__ */ jsx14(Text8, { fontSize: "sm", color: "gray.700", children: user.nome }),
989
+ /* @__PURE__ */ jsx14(Button3, { size: "xs", variant: "outline", onClick: logout, children: "Sair" })
990
+ ] }) : null
991
+ ] }),
992
+ /* @__PURE__ */ jsx14(Box8, { as: "main", flex: "1", p: 6, minW: 0, children: /* @__PURE__ */ jsx14(Outlet, {}) })
993
+ ] })
994
+ ] });
995
+ }
996
+
997
+ // src/security/RouteGuard.tsx
998
+ import { Fragment, jsx as jsx15 } from "react/jsx-runtime";
999
+ function RouteGuard({
1000
+ permission,
1001
+ children
1002
+ }) {
1003
+ const { status, hasPermission } = useOonAuth();
1004
+ if (status === "loading") return /* @__PURE__ */ jsx15(LoadingScreen, {});
1005
+ if (status === "unauthenticated") return /* @__PURE__ */ jsx15(LoadingScreen, { message: "Redirecionando para o login..." });
1006
+ if (!hasPermission(permission)) {
1007
+ return /* @__PURE__ */ jsx15(LoadingScreen, { message: "Voc\xEA n\xE3o tem acesso a esta tela." });
1008
+ }
1009
+ return /* @__PURE__ */ jsx15(Fragment, { children });
1010
+ }
1011
+
1012
+ // src/app/createOonRouter.tsx
1013
+ import { jsx as jsx16 } from "react/jsx-runtime";
1014
+ function createOonRouter({ app, menu, routes, security }) {
1015
+ const guardEnabled = security?.enableRouteGuard !== false;
1016
+ const publicRoutes = routes.filter((r) => r.public).map((r) => ({ path: r.path, element: r.element }));
1017
+ const privateRoutes = routes.filter((r) => !r.public).map((r) => {
1018
+ const element = guardEnabled ? /* @__PURE__ */ jsx16(RouteGuard, { permission: r.permissions, children: r.element }) : r.element;
1019
+ return r.path === "/" ? { index: true, element } : { path: r.path.replace(/^\//, ""), element };
1020
+ });
1021
+ const tree = [
1022
+ ...publicRoutes,
1023
+ {
1024
+ path: "/",
1025
+ element: /* @__PURE__ */ jsx16(AuthenticatedLayout, { app, menu }),
1026
+ children: privateRoutes
1027
+ }
1028
+ ];
1029
+ return createBrowserRouter(tree);
1030
+ }
1031
+
1032
+ // src/app/OonApp.tsx
1033
+ import { jsx as jsx17 } from "react/jsx-runtime";
1034
+ function OonApp({ config }) {
1035
+ const { http, storage, router } = useMemo3(() => {
1036
+ const storage2 = createTokenStorage(config.app.id);
1037
+ const loginUrl = config.auth?.loginUrl ?? config.api.meusAppsUrl;
1038
+ const http2 = createRestClient({
1039
+ api: config.api,
1040
+ appId: config.app.id,
1041
+ getToken: () => storage2.get(),
1042
+ onUnauthorized: () => {
1043
+ storage2.clear();
1044
+ if (loginUrl) window.location.href = loginUrl;
1045
+ }
1046
+ });
1047
+ const { routes, menu } = resolveRegistry(config);
1048
+ const router2 = createOonRouter({ app: config.app, menu, routes, security: config.security });
1049
+ return { http: http2, storage: storage2, router: router2 };
1050
+ }, [config]);
1051
+ return /* @__PURE__ */ jsx17(OonProviders, { config, http, storage, children: /* @__PURE__ */ jsx17(RouterProvider, { router }) });
1052
+ }
1053
+
1054
+ // src/start.tsx
1055
+ import { jsx as jsx18 } from "react/jsx-runtime";
1056
+ function validateConfig(config) {
1057
+ if (!config?.app?.id) throw new Error("oonCoreFront.start: config.app.id \xE9 obrigat\xF3rio.");
1058
+ if (!config?.api?.baseUrl) throw new Error("oonCoreFront.start: config.api.baseUrl \xE9 obrigat\xF3rio.");
1059
+ }
1060
+ function applyDocumentMeta(config) {
1061
+ if (config.app.title) document.title = config.app.title;
1062
+ if (config.app.favicon) {
1063
+ let link = document.querySelector('link[rel="icon"]');
1064
+ if (!link) {
1065
+ link = document.createElement("link");
1066
+ link.rel = "icon";
1067
+ document.head.appendChild(link);
1068
+ }
1069
+ link.href = config.app.favicon;
1070
+ }
1071
+ }
1072
+ function hardenConsole(config) {
1073
+ if (config.security?.disableConsoleInProduction && import.meta.env?.PROD) {
1074
+ const noop = () => {
1075
+ };
1076
+ console.log = noop;
1077
+ console.debug = noop;
1078
+ console.info = noop;
1079
+ }
1080
+ }
1081
+ function start(config) {
1082
+ validateConfig(config);
1083
+ applyDocumentMeta(config);
1084
+ hardenConsole(config);
1085
+ const rootId = config.rootElementId ?? "root";
1086
+ const el = document.getElementById(rootId);
1087
+ if (!el) throw new Error(`oonCoreFront.start: elemento #${rootId} n\xE3o encontrado no DOM.`);
1088
+ createRoot(el).render(
1089
+ /* @__PURE__ */ jsx18(StrictMode, { children: /* @__PURE__ */ jsx18(OonApp, { config }) })
1090
+ );
1091
+ }
1092
+ var oonCoreFront = { start };
1093
+
1094
+ // src/manifest.ts
1095
+ function manifestToConfig(manifest, runtime) {
1096
+ const views = [];
1097
+ for (const c of manifest.collections ?? []) {
1098
+ const view = {
1099
+ type: "collection",
1100
+ model: c.model,
1101
+ mode: c.mode ?? "dynamic",
1102
+ path: c.path,
1103
+ label: c.label,
1104
+ section: c.section
1105
+ };
1106
+ views.push(view);
1107
+ }
1108
+ for (const p of manifest.pipelines ?? []) {
1109
+ const model = p.model ?? p.name;
1110
+ if (!model) continue;
1111
+ const view = {
1112
+ type: "pipeline",
1113
+ model,
1114
+ label: p.label ?? p.name,
1115
+ path: p.path,
1116
+ stageField: p.stageField,
1117
+ section: p.section
1118
+ };
1119
+ views.push(view);
1120
+ }
1121
+ for (const d of manifest.documents ?? []) {
1122
+ const view = {
1123
+ type: "document",
1124
+ model: d.model,
1125
+ label: d.label,
1126
+ path: d.path,
1127
+ section: d.section,
1128
+ approval: d.approval,
1129
+ attachments: d.attachments
1130
+ };
1131
+ views.push(view);
1132
+ }
1133
+ return {
1134
+ app: {
1135
+ id: manifest.slug,
1136
+ name: manifest.name,
1137
+ title: manifest.name
1138
+ },
1139
+ api: {
1140
+ baseUrl: runtime.apiBaseUrl,
1141
+ meusAppsUrl: runtime.meusAppsUrl,
1142
+ assistantBaseUrl: runtime.assistantBaseUrl,
1143
+ versionPrefix: runtime.versionPrefix ?? ""
1144
+ },
1145
+ auth: { mode: "bearer", tokenParam: "code" },
1146
+ security: {
1147
+ enableRouteGuard: true,
1148
+ enablePermissionGate: true,
1149
+ redactAssistantContext: true,
1150
+ disableConsoleInProduction: true
1151
+ },
1152
+ ui: { views }
1153
+ };
1154
+ }
1155
+ function startFromManifest(manifest, runtime) {
1156
+ start(manifestToConfig(manifest, runtime));
1157
+ }
1158
+
1159
+ // src/define/views.ts
1160
+ function defineCollectionView(def) {
1161
+ return { type: "collection", ...def };
1162
+ }
1163
+ function defineDocumentView(def) {
1164
+ return { type: "document", ...def };
1165
+ }
1166
+ function definePipelineView(def) {
1167
+ return { type: "pipeline", ...def };
1168
+ }
1169
+ function defineDashboard(def) {
1170
+ return { type: "dashboard", ...def };
1171
+ }
1172
+ function defineOonModule(mod) {
1173
+ return mod;
1174
+ }
1175
+
1176
+ // src/components/CoreIntegration.tsx
1177
+ import { Badge, Box as Box9, Button as Button4, Flex as Flex7, Heading as Heading6, Stack as Stack6, Text as Text9 } from "@chakra-ui/react";
1178
+ import { useMutation as useMutation2 } from "@tanstack/react-query";
1179
+ import { jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
1180
+ function CoreIntegration({ label = "Integra\xE7\xF5es" }) {
1181
+ const { http } = useOonApi();
1182
+ const metadata = useCoreMetadata();
1183
+ const reprocess = useMutation2({
1184
+ mutationFn: () => http.post("/integrations/actions/process-active", {}, { headers: { "x-oon-origin": "integration" } })
1185
+ });
1186
+ if (metadata.isLoading) return /* @__PURE__ */ jsx19(LoadingScreen, {});
1187
+ if (metadata.isError) return /* @__PURE__ */ jsx19(ErrorState, { error: metadata.error });
1188
+ const integrations = metadata.data?.integrations ?? [];
1189
+ return /* @__PURE__ */ jsxs10(Stack6, { gap: 6, children: [
1190
+ /* @__PURE__ */ jsxs10(Flex7, { align: "center", justify: "space-between", children: [
1191
+ /* @__PURE__ */ jsx19(Heading6, { size: "lg", children: label }),
1192
+ /* @__PURE__ */ jsx19(Button4, { size: "sm", variant: "outline", loading: reprocess.isPending, onClick: () => reprocess.mutate(), children: "Reprocessar ativos" })
1193
+ ] }),
1194
+ /* @__PURE__ */ jsx19(Stack6, { gap: 3, children: integrations.length === 0 ? /* @__PURE__ */ jsx19(Text9, { color: "gray.400", children: "Nenhuma integra\xE7\xE3o declarada." }) : integrations.map((it, idx) => /* @__PURE__ */ jsx19(
1195
+ Box9,
1196
+ {
1197
+ borderWidth: "1px",
1198
+ borderColor: "gray.200",
1199
+ borderRadius: "md",
1200
+ bg: "white",
1201
+ p: 4,
1202
+ children: /* @__PURE__ */ jsxs10(Flex7, { align: "center", justify: "space-between", children: [
1203
+ /* @__PURE__ */ jsx19(Text9, { fontWeight: "medium", children: it.label ?? it.name ?? "Integra\xE7\xE3o" }),
1204
+ /* @__PURE__ */ jsx19(Badge, { colorPalette: it.status === "erro" ? "red" : it.status === "ativo" ? "green" : "gray", children: it.status ?? "\u2014" })
1205
+ ] })
1206
+ },
1207
+ it.id ?? it.name ?? idx
1208
+ )) })
1209
+ ] });
1210
+ }
1211
+
1212
+ // src/components/CoreCurrency.tsx
1213
+ import { Button as Button5, Flex as Flex8, Heading as Heading7, Stack as Stack7, Text as Text10 } from "@chakra-ui/react";
1214
+ import { useMutation as useMutation3, useQueryClient as useQueryClient2 } from "@tanstack/react-query";
1215
+ import { jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
1216
+ function CoreCurrency({ model = "Moeda", label = "Multi-moedas", endpoint = "/moedas", columns }) {
1217
+ const { http } = useOonApi();
1218
+ const queryClient = useQueryClient2();
1219
+ const updateRates = useMutation3({
1220
+ mutationFn: () => http.post(`${endpoint}/actions/update-rates`, {}, { headers: { "x-oon-origin": "integration" } }),
1221
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ["oon", "list", endpoint] })
1222
+ });
1223
+ return /* @__PURE__ */ jsxs11(Stack7, { gap: 6, children: [
1224
+ /* @__PURE__ */ jsxs11(Flex8, { align: "center", justify: "space-between", children: [
1225
+ /* @__PURE__ */ jsx20(Heading7, { size: "lg", children: label }),
1226
+ /* @__PURE__ */ jsx20(Button5, { size: "sm", colorPalette: "blue", loading: updateRates.isPending, onClick: () => updateRates.mutate(), children: "Atualizar cota\xE7\xF5es" })
1227
+ ] }),
1228
+ updateRates.isError ? /* @__PURE__ */ jsx20(Text10, { fontSize: "sm", color: "red.600", children: updateRates.error?.message }) : null,
1229
+ /* @__PURE__ */ jsx20(CoreCollection, { model, label: "", mode: columns ? "full" : "dynamic", endpoint, columns })
1230
+ ] });
1231
+ }
1232
+
1233
+ // src/components/CoreAssistant.tsx
1234
+ import { useState as useState5 } from "react";
1235
+ import { Box as Box10, Button as Button6, Flex as Flex9, Heading as Heading8, Input as Input3, Stack as Stack8, Text as Text11 } from "@chakra-ui/react";
1236
+ import { useMutation as useMutation4 } from "@tanstack/react-query";
1237
+ import { jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
1238
+ function CoreAssistant({ assistantCode, label = "Assistente", entity, endpoint = "/assistant-runs" }) {
1239
+ const { http } = useOonApi();
1240
+ const [messages, setMessages] = useState5([]);
1241
+ const [input, setInput] = useState5("");
1242
+ const ask = useMutation4({
1243
+ mutationFn: async (message) => {
1244
+ const { data } = await http.post(
1245
+ endpoint,
1246
+ {
1247
+ assistantCode,
1248
+ entity,
1249
+ message,
1250
+ // Contexto redigido: a Central pode enriquecer, mas o Core não envia PII por padrão.
1251
+ context: { redactionPolicy: "default" }
1252
+ },
1253
+ { headers: { "x-oon-origin": "assistant" } }
1254
+ );
1255
+ return data.reply ?? data.message ?? "";
1256
+ },
1257
+ onSuccess: (reply) => setMessages((prev) => [...prev, { role: "assistant", content: reply }])
1258
+ });
1259
+ const send = () => {
1260
+ const text = input.trim();
1261
+ if (!text) return;
1262
+ setMessages((prev) => [...prev, { role: "user", content: text }]);
1263
+ setInput("");
1264
+ ask.mutate(text);
1265
+ };
1266
+ return /* @__PURE__ */ jsxs12(Stack8, { gap: 4, maxW: "560px", children: [
1267
+ /* @__PURE__ */ jsx21(Heading8, { size: "md", children: label }),
1268
+ /* @__PURE__ */ jsx21(Box10, { borderWidth: "1px", borderColor: "gray.200", borderRadius: "md", bg: "white", p: 4, minH: "240px", children: /* @__PURE__ */ jsxs12(Stack8, { gap: 3, children: [
1269
+ messages.length === 0 ? /* @__PURE__ */ jsx21(Text11, { color: "gray.400", fontSize: "sm", children: "Fa\xE7a uma pergunta ao assistente." }) : messages.map((m, idx) => /* @__PURE__ */ jsx21(Flex9, { justify: m.role === "user" ? "flex-end" : "flex-start", children: /* @__PURE__ */ jsx21(
1270
+ Box10,
1271
+ {
1272
+ bg: m.role === "user" ? "blue.500" : "gray.100",
1273
+ color: m.role === "user" ? "white" : "gray.800",
1274
+ px: 3,
1275
+ py: 2,
1276
+ borderRadius: "md",
1277
+ maxW: "80%",
1278
+ fontSize: "sm",
1279
+ children: m.content
1280
+ }
1281
+ ) }, idx)),
1282
+ ask.isPending ? /* @__PURE__ */ jsx21(Text11, { color: "gray.400", fontSize: "sm", children: "Pensando\u2026" }) : null,
1283
+ ask.isError ? /* @__PURE__ */ jsx21(Text11, { color: "red.600", fontSize: "sm", children: ask.error?.message }) : null
1284
+ ] }) }),
1285
+ /* @__PURE__ */ jsxs12(Flex9, { gap: 2, children: [
1286
+ /* @__PURE__ */ jsx21(
1287
+ Input3,
1288
+ {
1289
+ size: "sm",
1290
+ value: input,
1291
+ placeholder: "Digite sua mensagem...",
1292
+ onChange: (e) => setInput(e.currentTarget.value),
1293
+ onKeyDown: (e) => {
1294
+ if (e.key === "Enter") send();
1295
+ }
1296
+ }
1297
+ ),
1298
+ /* @__PURE__ */ jsx21(Button6, { size: "sm", colorPalette: "blue", onClick: send, loading: ask.isPending, children: "Enviar" })
1299
+ ] })
1300
+ ] });
1301
+ }
1302
+
1303
+ // src/security/PermissionGate.tsx
1304
+ import { Fragment as Fragment2, jsx as jsx22 } from "react/jsx-runtime";
1305
+ function PermissionGate({
1306
+ permission,
1307
+ fallback = null,
1308
+ children
1309
+ }) {
1310
+ const { hasPermission } = useOonAuth();
1311
+ return /* @__PURE__ */ jsx22(Fragment2, { children: hasPermission(permission) ? children : fallback });
1312
+ }
1313
+ export {
1314
+ CoreAssistant,
1315
+ CoreCollection,
1316
+ CoreCurrency,
1317
+ CoreDashboard,
1318
+ CoreDocument,
1319
+ CoreIntegration,
1320
+ CorePipeline,
1321
+ PermissionGate,
1322
+ defineCollectionView,
1323
+ defineDashboard,
1324
+ defineDocumentView,
1325
+ defineOonModule,
1326
+ definePipelineView,
1327
+ manifestToConfig,
1328
+ oonCoreFront,
1329
+ start,
1330
+ startFromManifest,
1331
+ useCoreMetadata,
1332
+ useModelSchema,
1333
+ useOonApi,
1334
+ useOonAuth,
1335
+ useOonResource
1336
+ };
1337
+ //# sourceMappingURL=index.js.map