@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/LICENSE +91 -0
- package/README.md +120 -0
- package/bin/cli.js +100 -0
- package/dist/index.d.ts +533 -0
- package/dist/index.js +1337 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
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
|