@oondemand/oon-core-front 0.1.4 → 0.3.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/README.md +2 -121
- package/dist/index.d.ts +18 -23
- package/dist/index.js +2057 -382
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { StrictMode } from "react";
|
|
|
3
3
|
import { createRoot } from "react-dom/client";
|
|
4
4
|
|
|
5
5
|
// src/app/OonApp.tsx
|
|
6
|
-
import { useMemo as
|
|
6
|
+
import { useMemo as useMemo4 } from "react";
|
|
7
7
|
import { RouterProvider } from "react-router-dom";
|
|
8
8
|
|
|
9
9
|
// src/api/createRestClient.ts
|
|
@@ -56,7 +56,9 @@ function createRestClient(deps) {
|
|
|
56
56
|
});
|
|
57
57
|
instance.interceptors.request.use((config) => {
|
|
58
58
|
const token = getToken();
|
|
59
|
-
if (token
|
|
59
|
+
if (token && !config.headers.Authorization) {
|
|
60
|
+
config.headers.Authorization = `Bearer ${token}`;
|
|
61
|
+
}
|
|
60
62
|
if (appId) config.headers["x-oon-app"] = appId;
|
|
61
63
|
return config;
|
|
62
64
|
});
|
|
@@ -64,7 +66,9 @@ function createRestClient(deps) {
|
|
|
64
66
|
(res) => res,
|
|
65
67
|
(error) => {
|
|
66
68
|
const normalized = normalizeError(error);
|
|
67
|
-
|
|
69
|
+
const requestPath = String(error.config?.url || "").split("?")[0].replace(/\/$/, "");
|
|
70
|
+
const isLogin = requestPath.endsWith("/auth/autenticar");
|
|
71
|
+
if (normalized.status === 401 && !isLogin) onUnauthorized?.();
|
|
68
72
|
return Promise.reject(normalized);
|
|
69
73
|
}
|
|
70
74
|
);
|
|
@@ -120,7 +124,7 @@ function captureTokenFromUrl(tokenParam = "code") {
|
|
|
120
124
|
|
|
121
125
|
// src/app/OonProviders.tsx
|
|
122
126
|
import { useMemo as useMemo2 } from "react";
|
|
123
|
-
import { ChakraProvider
|
|
127
|
+
import { ChakraProvider } from "@chakra-ui/react";
|
|
124
128
|
import { QueryClientProvider } from "@tanstack/react-query";
|
|
125
129
|
|
|
126
130
|
// src/config/queryClient.ts
|
|
@@ -140,6 +144,82 @@ function createOonQueryClient() {
|
|
|
140
144
|
});
|
|
141
145
|
}
|
|
142
146
|
|
|
147
|
+
// src/config/theme.ts
|
|
148
|
+
import { createSystem, defaultConfig, defineConfig, mergeConfigs } from "@chakra-ui/react";
|
|
149
|
+
var brandColors = {
|
|
150
|
+
brand: {
|
|
151
|
+
25: { value: "#F8FCFF" },
|
|
152
|
+
50: { value: "#EEF8FD" },
|
|
153
|
+
75: { value: "#E2F3FB" },
|
|
154
|
+
100: { value: "#C7E8F7" },
|
|
155
|
+
200: { value: "#82C8E8" },
|
|
156
|
+
300: { value: "#3FA6E0" },
|
|
157
|
+
350: { value: "#0F8DD0" },
|
|
158
|
+
400: { value: "#0580B9" },
|
|
159
|
+
500: { value: "#0474AF" },
|
|
160
|
+
600: { value: "#036491" },
|
|
161
|
+
700: { value: "#014364" },
|
|
162
|
+
800: { value: "#073A52" },
|
|
163
|
+
850: { value: "#082F3F" },
|
|
164
|
+
900: { value: "#07262E" }
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
var oonThemeConfig = defineConfig({
|
|
168
|
+
globalCss: {
|
|
169
|
+
"html, body, #root": {
|
|
170
|
+
minHeight: "100%"
|
|
171
|
+
},
|
|
172
|
+
body: {
|
|
173
|
+
margin: 0,
|
|
174
|
+
bg: "#F8F9FA",
|
|
175
|
+
color: "#24323A",
|
|
176
|
+
fontFamily: "Poppins, Inter, system-ui, sans-serif",
|
|
177
|
+
fontSize: "14px"
|
|
178
|
+
},
|
|
179
|
+
"button, input, select, textarea": {
|
|
180
|
+
fontFamily: "inherit"
|
|
181
|
+
},
|
|
182
|
+
"::selection": {
|
|
183
|
+
bg: "#C7E8F7",
|
|
184
|
+
color: "#07262E"
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
theme: {
|
|
188
|
+
tokens: {
|
|
189
|
+
colors: brandColors,
|
|
190
|
+
fonts: {
|
|
191
|
+
body: { value: "Poppins, Inter, system-ui, sans-serif" },
|
|
192
|
+
heading: { value: "Poppins, Inter, system-ui, sans-serif" }
|
|
193
|
+
},
|
|
194
|
+
radii: {
|
|
195
|
+
oon: { value: "10px" }
|
|
196
|
+
},
|
|
197
|
+
shadows: {
|
|
198
|
+
oon: { value: "0 8px 24px rgba(7, 38, 46, 0.08)" }
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
semanticTokens: {
|
|
202
|
+
colors: {
|
|
203
|
+
brand: {
|
|
204
|
+
solid: { value: "{colors.brand.500}" },
|
|
205
|
+
contrast: { value: "white" },
|
|
206
|
+
fg: { value: "{colors.brand.700}" },
|
|
207
|
+
muted: { value: "{colors.brand.50}" },
|
|
208
|
+
subtle: { value: "{colors.brand.75}" },
|
|
209
|
+
emphasized: { value: "{colors.brand.600}" },
|
|
210
|
+
focusRing: { value: "{colors.brand.500}" }
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
function createOonSystem(themeOverride) {
|
|
217
|
+
const base = mergeConfigs(defaultConfig, oonThemeConfig);
|
|
218
|
+
const config = themeOverride ? mergeConfigs(base, defineConfig(themeOverride)) : base;
|
|
219
|
+
return createSystem(config);
|
|
220
|
+
}
|
|
221
|
+
var oonSystem = createOonSystem();
|
|
222
|
+
|
|
143
223
|
// src/api/ApiProvider.tsx
|
|
144
224
|
import { createContext, useContext, useMemo } from "react";
|
|
145
225
|
import { useQuery } from "@tanstack/react-query";
|
|
@@ -281,55 +361,212 @@ function useModelSchema(name) {
|
|
|
281
361
|
}
|
|
282
362
|
|
|
283
363
|
// src/security/AuthProvider.tsx
|
|
284
|
-
import {
|
|
364
|
+
import {
|
|
365
|
+
createContext as createContext3,
|
|
366
|
+
useCallback as useCallback2,
|
|
367
|
+
useContext as useContext3,
|
|
368
|
+
useEffect as useEffect2,
|
|
369
|
+
useState as useState2
|
|
370
|
+
} from "react";
|
|
371
|
+
|
|
372
|
+
// src/security/ActivationProvider.tsx
|
|
373
|
+
import {
|
|
374
|
+
createContext as createContext2,
|
|
375
|
+
useCallback,
|
|
376
|
+
useContext as useContext2,
|
|
377
|
+
useEffect,
|
|
378
|
+
useState
|
|
379
|
+
} from "react";
|
|
285
380
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
286
|
-
var
|
|
287
|
-
function
|
|
288
|
-
|
|
289
|
-
|
|
381
|
+
var ActivationContext = createContext2(null);
|
|
382
|
+
function mapStatus(data) {
|
|
383
|
+
if (data.status === "ativa") return "active";
|
|
384
|
+
if (data.status === "suspensa") return "suspended";
|
|
385
|
+
if (data.status === "revogada") return "revoked";
|
|
386
|
+
if (data.status === "configurando") return "configuring";
|
|
387
|
+
if (data.status === "erro" && !data.podeAtivar) return "error";
|
|
388
|
+
return "notActivated";
|
|
389
|
+
}
|
|
390
|
+
function ActivationProvider({
|
|
391
|
+
http,
|
|
392
|
+
children
|
|
393
|
+
}) {
|
|
290
394
|
const [status, setStatus] = useState("loading");
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
395
|
+
const [details, setDetails] = useState(null);
|
|
396
|
+
const refresh = useCallback(async () => {
|
|
397
|
+
setStatus("loading");
|
|
398
|
+
try {
|
|
399
|
+
const response = await http.get("/ativacao/status");
|
|
400
|
+
setDetails(response.data);
|
|
401
|
+
setStatus(mapStatus(response.data));
|
|
402
|
+
} catch {
|
|
403
|
+
setStatus("error");
|
|
296
404
|
}
|
|
405
|
+
}, [http]);
|
|
406
|
+
useEffect(() => {
|
|
407
|
+
void refresh();
|
|
408
|
+
}, [refresh]);
|
|
409
|
+
return /* @__PURE__ */ jsx2(ActivationContext.Provider, { value: { status, details, refresh }, children });
|
|
410
|
+
}
|
|
411
|
+
function useOptionalActivation() {
|
|
412
|
+
return useContext2(ActivationContext);
|
|
413
|
+
}
|
|
414
|
+
function useActivation() {
|
|
415
|
+
const context = useOptionalActivation();
|
|
416
|
+
if (!context) {
|
|
417
|
+
throw new Error(
|
|
418
|
+
"useActivation deve ser usado dentro de <ActivationProvider>."
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
return context;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// src/security/AuthProvider.tsx
|
|
425
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
426
|
+
var AuthContext = createContext3(null);
|
|
427
|
+
function errorStatus(error) {
|
|
428
|
+
return error?.status;
|
|
429
|
+
}
|
|
430
|
+
function encodeBasicCredentials(email, password) {
|
|
431
|
+
const bytes = new TextEncoder().encode(`${email}:${password}`);
|
|
432
|
+
let binary = "";
|
|
433
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
434
|
+
return window.btoa(binary);
|
|
435
|
+
}
|
|
436
|
+
function AuthProvider({
|
|
437
|
+
http,
|
|
438
|
+
storage,
|
|
439
|
+
auth,
|
|
440
|
+
loginUrl,
|
|
441
|
+
children
|
|
442
|
+
}) {
|
|
443
|
+
const tokenParam = auth?.tokenParam ?? "code";
|
|
444
|
+
const devToken = auth?.devToken?.trim() || null;
|
|
445
|
+
const [user, setUser] = useState2(null);
|
|
446
|
+
const [status, setStatus] = useState2("loading");
|
|
447
|
+
const activation = useOptionalActivation();
|
|
448
|
+
const redirectToExternalLogin = useCallback2(() => {
|
|
449
|
+
if (loginUrl) window.location.href = loginUrl;
|
|
297
450
|
}, [loginUrl]);
|
|
298
|
-
const
|
|
451
|
+
const applyValidationError = useCallback2(
|
|
452
|
+
(error) => {
|
|
453
|
+
const currentStatus = errorStatus(error);
|
|
454
|
+
setUser(null);
|
|
455
|
+
if (currentStatus === 403) {
|
|
456
|
+
setStatus("forbidden");
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (currentStatus === 401) {
|
|
460
|
+
storage.clear();
|
|
461
|
+
setStatus("unauthenticated");
|
|
462
|
+
redirectToExternalLogin();
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
setStatus("error");
|
|
466
|
+
},
|
|
467
|
+
[storage, redirectToExternalLogin]
|
|
468
|
+
);
|
|
469
|
+
const validateSession = useCallback2(async () => {
|
|
470
|
+
const response = await http.get(
|
|
471
|
+
"/auth/validar-token"
|
|
472
|
+
);
|
|
473
|
+
setUser(response.data.usuario);
|
|
474
|
+
setStatus("authenticated");
|
|
475
|
+
}, [http]);
|
|
476
|
+
const retry = useCallback2(async () => {
|
|
477
|
+
setStatus("loading");
|
|
478
|
+
try {
|
|
479
|
+
await validateSession();
|
|
480
|
+
} catch (error) {
|
|
481
|
+
applyValidationError(error);
|
|
482
|
+
throw error;
|
|
483
|
+
}
|
|
484
|
+
}, [validateSession, applyValidationError]);
|
|
485
|
+
const login = useCallback2(
|
|
486
|
+
async (email, password) => {
|
|
487
|
+
setStatus("loading");
|
|
488
|
+
setUser(null);
|
|
489
|
+
try {
|
|
490
|
+
const response = await http.post(
|
|
491
|
+
"/auth/autenticar",
|
|
492
|
+
{},
|
|
493
|
+
{
|
|
494
|
+
headers: {
|
|
495
|
+
Authorization: `Basic ${encodeBasicCredentials(email, password)}`
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
);
|
|
499
|
+
if (!response.data.token) {
|
|
500
|
+
const error = {
|
|
501
|
+
code: "AUTH_TOKEN_MISSING",
|
|
502
|
+
message: "Token n\xE3o retornado pelo backend.",
|
|
503
|
+
status: 502
|
|
504
|
+
};
|
|
505
|
+
throw error;
|
|
506
|
+
}
|
|
507
|
+
storage.set(response.data.token);
|
|
508
|
+
await validateSession();
|
|
509
|
+
} catch (error) {
|
|
510
|
+
applyValidationError(error);
|
|
511
|
+
throw error;
|
|
512
|
+
}
|
|
513
|
+
},
|
|
514
|
+
[http, storage, validateSession, applyValidationError]
|
|
515
|
+
);
|
|
516
|
+
const logout = useCallback2(() => {
|
|
299
517
|
storage.clear();
|
|
300
518
|
setUser(null);
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
519
|
+
setStatus("unauthenticated");
|
|
520
|
+
redirectToExternalLogin();
|
|
521
|
+
}, [storage, redirectToExternalLogin]);
|
|
522
|
+
useEffect2(() => {
|
|
304
523
|
let active = true;
|
|
524
|
+
if (activation && !["active", "error"].includes(activation.status)) {
|
|
525
|
+
setUser(null);
|
|
526
|
+
setStatus("unauthenticated");
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
305
529
|
const incoming = captureTokenFromUrl(tokenParam);
|
|
306
|
-
|
|
307
|
-
const token =
|
|
530
|
+
const stored = storage.get();
|
|
531
|
+
const token = incoming ?? devToken ?? stored;
|
|
532
|
+
if (token && token !== stored) storage.set(token);
|
|
308
533
|
if (!token) {
|
|
309
|
-
|
|
534
|
+
setUser(null);
|
|
535
|
+
setStatus("unauthenticated");
|
|
536
|
+
redirectToExternalLogin();
|
|
310
537
|
return;
|
|
311
538
|
}
|
|
312
|
-
|
|
539
|
+
setStatus("loading");
|
|
540
|
+
http.get("/auth/validar-token").then((response) => {
|
|
313
541
|
if (!active) return;
|
|
314
|
-
setUser(
|
|
542
|
+
setUser(response.data.usuario);
|
|
315
543
|
setStatus("authenticated");
|
|
316
|
-
}).catch(() => {
|
|
544
|
+
}).catch((error) => {
|
|
317
545
|
if (!active) return;
|
|
318
|
-
|
|
319
|
-
redirectToLogin();
|
|
546
|
+
applyValidationError(error);
|
|
320
547
|
});
|
|
321
548
|
return () => {
|
|
322
549
|
active = false;
|
|
323
550
|
};
|
|
324
|
-
}, [
|
|
551
|
+
}, [
|
|
552
|
+
http,
|
|
553
|
+
storage,
|
|
554
|
+
tokenParam,
|
|
555
|
+
devToken,
|
|
556
|
+
redirectToExternalLogin,
|
|
557
|
+
applyValidationError,
|
|
558
|
+
activation?.status
|
|
559
|
+
]);
|
|
325
560
|
const roles = user?.tipo ? [user.tipo] : [];
|
|
326
|
-
const hasPermission =
|
|
561
|
+
const hasPermission = useCallback2(
|
|
327
562
|
(permission) => {
|
|
328
563
|
if (!permission) return true;
|
|
329
564
|
const required = Array.isArray(permission) ? permission : [permission];
|
|
330
565
|
if (required.length === 0) return true;
|
|
331
566
|
if (roles.includes("admin") || roles.includes("master")) return true;
|
|
332
|
-
return required.some(
|
|
567
|
+
return required.some(
|
|
568
|
+
(value2) => roles.includes(value2) || roles.some((role) => value2.startsWith(`${role}.`))
|
|
569
|
+
);
|
|
333
570
|
},
|
|
334
571
|
[roles]
|
|
335
572
|
);
|
|
@@ -339,27 +576,34 @@ function AuthProvider({ http, storage, auth, loginUrl, children }) {
|
|
|
339
576
|
status,
|
|
340
577
|
isAuthenticated: status === "authenticated",
|
|
341
578
|
hasPermission,
|
|
579
|
+
login,
|
|
580
|
+
retry,
|
|
342
581
|
logout
|
|
343
582
|
};
|
|
344
|
-
return /* @__PURE__ */
|
|
583
|
+
return /* @__PURE__ */ jsx3(AuthContext.Provider, { value, children });
|
|
345
584
|
}
|
|
346
585
|
function useOonAuth() {
|
|
347
|
-
const
|
|
348
|
-
if (!
|
|
349
|
-
|
|
586
|
+
const context = useContext3(AuthContext);
|
|
587
|
+
if (!context) {
|
|
588
|
+
throw new Error(
|
|
589
|
+
"useOonAuth deve ser usado dentro de <AuthProvider> (oonCoreFront.start)."
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
return context;
|
|
350
593
|
}
|
|
351
594
|
|
|
352
595
|
// src/app/OonProviders.tsx
|
|
353
|
-
import { jsx as
|
|
596
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
354
597
|
function OonProviders({ config, http, storage, children }) {
|
|
355
598
|
const queryClient = useMemo2(() => createOonQueryClient(), []);
|
|
599
|
+
const system = useMemo2(() => createOonSystem(config.theme), [config.theme]);
|
|
356
600
|
const loginUrl = config.auth?.loginUrl ?? config.api.meusAppsUrl;
|
|
357
|
-
return /* @__PURE__ */
|
|
601
|
+
return /* @__PURE__ */ jsx4(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx4(ChakraProvider, { value: system, children: /* @__PURE__ */ jsx4(ApiProvider, { http, children: /* @__PURE__ */ jsx4(ActivationProvider, { http, children: /* @__PURE__ */ jsx4(AuthProvider, { http, storage, auth: config.auth, loginUrl, children }) }) }) }) });
|
|
358
602
|
}
|
|
359
603
|
|
|
360
604
|
// src/components/CoreCollection.tsx
|
|
361
|
-
import { useState as
|
|
362
|
-
import { Box as
|
|
605
|
+
import { useState as useState6 } from "react";
|
|
606
|
+
import { Box as Box5, Heading as Heading3, Stack as Stack3, Text as Text6 } from "@chakra-ui/react";
|
|
363
607
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
364
608
|
|
|
365
609
|
// src/components/internal/fieldUtils.ts
|
|
@@ -369,17 +613,39 @@ function isEditableField(meta) {
|
|
|
369
613
|
}
|
|
370
614
|
function fieldLabel(meta) {
|
|
371
615
|
if (meta.label) return meta.label;
|
|
372
|
-
return meta.name.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (
|
|
616
|
+
return meta.name.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (character) => character.toUpperCase());
|
|
373
617
|
}
|
|
374
618
|
function columnsFromMeta(fields) {
|
|
375
|
-
return fields.filter(isEditableField).slice(0, 8).map((
|
|
619
|
+
return fields.filter(isEditableField).slice(0, 8).map((field) => ({ field: field.name, label: fieldLabel(field), kind: field.kind, sortable: true }));
|
|
620
|
+
}
|
|
621
|
+
function formRequired(field) {
|
|
622
|
+
const raw = field;
|
|
623
|
+
const options = raw.options;
|
|
624
|
+
const validation = raw.validation;
|
|
625
|
+
const schema = raw.schema;
|
|
626
|
+
return Boolean(
|
|
627
|
+
field.required || options && !Array.isArray(options) && typeof options === "object" && options.required || validation && typeof validation === "object" && validation.required || schema && typeof schema === "object" && schema.required
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
function formOptions(field) {
|
|
631
|
+
const rawOptions = field.options;
|
|
632
|
+
if (Array.isArray(rawOptions)) return rawOptions.filter((value) => typeof value === "string");
|
|
633
|
+
if (!rawOptions || typeof rawOptions !== "object") return void 0;
|
|
634
|
+
const options = rawOptions;
|
|
635
|
+
const candidates = [options.values, options.enum, options.options];
|
|
636
|
+
for (const candidate of candidates) {
|
|
637
|
+
if (Array.isArray(candidate)) return candidate.filter((value) => typeof value === "string");
|
|
638
|
+
}
|
|
639
|
+
return void 0;
|
|
376
640
|
}
|
|
377
641
|
function formFieldsFromMeta(fields) {
|
|
378
|
-
return fields.filter(isEditableField).map((
|
|
379
|
-
field:
|
|
380
|
-
label: fieldLabel(
|
|
381
|
-
kind:
|
|
382
|
-
|
|
642
|
+
return fields.filter(isEditableField).map((field) => ({
|
|
643
|
+
field: field.name,
|
|
644
|
+
label: fieldLabel(field),
|
|
645
|
+
kind: field.kind,
|
|
646
|
+
required: formRequired(field),
|
|
647
|
+
options: formOptions(field),
|
|
648
|
+
ref: field.ref
|
|
383
649
|
}));
|
|
384
650
|
}
|
|
385
651
|
function formatCell(value, kind) {
|
|
@@ -398,8 +664,8 @@ function formatCell(value, kind) {
|
|
|
398
664
|
return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(Number(value));
|
|
399
665
|
case "ref":
|
|
400
666
|
if (value && typeof value === "object") {
|
|
401
|
-
const
|
|
402
|
-
return String(
|
|
667
|
+
const object = value;
|
|
668
|
+
return String(object.nome ?? object.label ?? object.descricao ?? object._id ?? "\u2014");
|
|
403
669
|
}
|
|
404
670
|
return String(value);
|
|
405
671
|
default:
|
|
@@ -408,30 +674,35 @@ function formatCell(value, kind) {
|
|
|
408
674
|
}
|
|
409
675
|
|
|
410
676
|
// src/components/internal/DataGrid.tsx
|
|
411
|
-
import { useState as
|
|
412
|
-
import { Box as Box2, Button, Flex as Flex2, Input, Spacer, Table, Text as Text3 } from "@chakra-ui/react";
|
|
677
|
+
import { useDeferredValue, useState as useState3 } from "react";
|
|
678
|
+
import { Badge, Box as Box2, Button as Button2, Flex as Flex2, Input, NativeSelect, Spacer, Table, Text as Text3 } from "@chakra-ui/react";
|
|
413
679
|
import { keepPreviousData, useQuery as useQuery2 } from "@tanstack/react-query";
|
|
414
680
|
|
|
415
681
|
// src/shell/LoadingScreen.tsx
|
|
416
|
-
import { Flex, Spinner, Text } from "@chakra-ui/react";
|
|
417
|
-
import { jsx as
|
|
418
|
-
function LoadingScreen({
|
|
682
|
+
import { Button, Flex, Spinner, Text } from "@chakra-ui/react";
|
|
683
|
+
import { jsx as jsx5, jsxs } from "react/jsx-runtime";
|
|
684
|
+
function LoadingScreen({
|
|
685
|
+
message,
|
|
686
|
+
actionLabel,
|
|
687
|
+
onAction
|
|
688
|
+
}) {
|
|
419
689
|
return /* @__PURE__ */ jsxs(Flex, { direction: "column", align: "center", justify: "center", minH: "60vh", gap: 4, children: [
|
|
420
|
-
/* @__PURE__ */
|
|
421
|
-
message ? /* @__PURE__ */
|
|
690
|
+
/* @__PURE__ */ jsx5(Spinner, { size: "lg" }),
|
|
691
|
+
message ? /* @__PURE__ */ jsx5(Text, { color: "gray.500", fontSize: "sm", textAlign: "center", children: message }) : null,
|
|
692
|
+
actionLabel && onAction ? /* @__PURE__ */ jsx5(Button, { size: "sm", variant: "outline", onClick: onAction, children: actionLabel }) : null
|
|
422
693
|
] });
|
|
423
694
|
}
|
|
424
695
|
|
|
425
696
|
// src/shell/ErrorState.tsx
|
|
426
697
|
import { Box, Heading, Text as Text2 } from "@chakra-ui/react";
|
|
427
|
-
import { jsx as
|
|
698
|
+
import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
428
699
|
function ErrorState({
|
|
429
700
|
error,
|
|
430
701
|
title = "N\xE3o foi poss\xEDvel carregar"
|
|
431
702
|
}) {
|
|
432
703
|
return /* @__PURE__ */ jsxs2(Box, { borderWidth: "1px", borderColor: "red.200", bg: "red.50", borderRadius: "md", p: 4, children: [
|
|
433
|
-
/* @__PURE__ */
|
|
434
|
-
/* @__PURE__ */
|
|
704
|
+
/* @__PURE__ */ jsx6(Heading, { size: "sm", color: "red.700", children: title }),
|
|
705
|
+
/* @__PURE__ */ jsx6(Text2, { mt: 1, fontSize: "sm", color: "red.600", children: error?.message ?? "Erro inesperado." }),
|
|
435
706
|
error?.requestId ? /* @__PURE__ */ jsxs2(Text2, { mt: 2, fontSize: "xs", color: "red.400", children: [
|
|
436
707
|
"requestId: ",
|
|
437
708
|
error.requestId
|
|
@@ -440,8 +711,44 @@ function ErrorState({
|
|
|
440
711
|
}
|
|
441
712
|
|
|
442
713
|
// src/components/internal/DataGrid.tsx
|
|
443
|
-
import { jsx as
|
|
444
|
-
|
|
714
|
+
import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
715
|
+
function SearchIcon() {
|
|
716
|
+
return /* @__PURE__ */ jsxs3("svg", { viewBox: "0 0 20 20", width: "17", height: "17", "aria-hidden": "true", children: [
|
|
717
|
+
/* @__PURE__ */ jsx7("circle", { cx: "8.5", cy: "8.5", r: "5.25", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
718
|
+
/* @__PURE__ */ jsx7("path", { d: "m12.4 12.4 4 4", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
|
|
719
|
+
] });
|
|
720
|
+
}
|
|
721
|
+
function EditIcon() {
|
|
722
|
+
return /* @__PURE__ */ jsxs3("svg", { viewBox: "0 0 20 20", width: "15", height: "15", "aria-hidden": "true", children: [
|
|
723
|
+
/* @__PURE__ */ jsx7("path", { d: "M4 14.5V16h1.5L14.7 6.8l-1.5-1.5L4 14.5Z", fill: "none", stroke: "currentColor", strokeWidth: "1.45", strokeLinejoin: "round" }),
|
|
724
|
+
/* @__PURE__ */ jsx7("path", { d: "m12.5 6 1.5-1.5a1.05 1.05 0 0 1 1.5 0l.1.1a1.05 1.05 0 0 1 0 1.5L14.1 7.6", fill: "none", stroke: "currentColor", strokeWidth: "1.45", strokeLinecap: "round" })
|
|
725
|
+
] });
|
|
726
|
+
}
|
|
727
|
+
function TrashIcon() {
|
|
728
|
+
return /* @__PURE__ */ jsxs3("svg", { viewBox: "0 0 20 20", width: "15", height: "15", "aria-hidden": "true", children: [
|
|
729
|
+
/* @__PURE__ */ jsx7("path", { d: "M5.5 6.5h9l-.6 9H6.1l-.6-9Zm2-2h5l.7 2H6.8l.7-2Z", fill: "none", stroke: "currentColor", strokeWidth: "1.35", strokeLinejoin: "round" }),
|
|
730
|
+
/* @__PURE__ */ jsx7("path", { d: "M4.5 6.5h11", fill: "none", stroke: "currentColor", strokeWidth: "1.35", strokeLinecap: "round" })
|
|
731
|
+
] });
|
|
732
|
+
}
|
|
733
|
+
function ChevronIcon({ direction }) {
|
|
734
|
+
return /* @__PURE__ */ jsx7("svg", { viewBox: "0 0 20 20", width: "17", height: "17", "aria-hidden": "true", style: { transform: direction === "right" ? "rotate(180deg)" : void 0 }, children: /* @__PURE__ */ jsx7("path", { d: "m12 5-5 5 5 5", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
735
|
+
}
|
|
736
|
+
function PlusIcon() {
|
|
737
|
+
return /* @__PURE__ */ jsx7("svg", { viewBox: "0 0 20 20", width: "17", height: "17", "aria-hidden": "true", children: /* @__PURE__ */ jsx7("path", { d: "M10 4v12M4 10h12", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }) });
|
|
738
|
+
}
|
|
739
|
+
function renderCell(value, kind) {
|
|
740
|
+
const formatted = formatCell(value, kind);
|
|
741
|
+
if (kind === "enum") {
|
|
742
|
+
const normalized = String(value ?? "").toLowerCase();
|
|
743
|
+
const palette = /erro|cancel|inativo|recus|critica/.test(normalized) ? "red" : /pendente|aguardando|rascunho/.test(normalized) ? "orange" : /processando|conferencia|integrando|validando/.test(normalized) ? "blue" : /ativo|sucesso|conclu|aplicada|disponivel|aprov/.test(normalized) ? "green" : "gray";
|
|
744
|
+
return /* @__PURE__ */ jsx7(Badge, { colorPalette: palette, variant: "subtle", borderRadius: "full", px: "7px", py: "3px", fontSize: "9px", fontWeight: "600", textTransform: "capitalize", children: formatted });
|
|
745
|
+
}
|
|
746
|
+
if (kind === "boolean") {
|
|
747
|
+
const active = value === true || value === "true";
|
|
748
|
+
return /* @__PURE__ */ jsx7(Badge, { colorPalette: active ? "green" : "gray", variant: "subtle", borderRadius: "full", px: "7px", py: "3px", fontSize: "9px", fontWeight: "600", children: formatted });
|
|
749
|
+
}
|
|
750
|
+
return formatted;
|
|
751
|
+
}
|
|
445
752
|
function DataGrid({
|
|
446
753
|
resource,
|
|
447
754
|
resourceKey,
|
|
@@ -452,104 +759,544 @@ function DataGrid({
|
|
|
452
759
|
onDelete,
|
|
453
760
|
toolbarExtra
|
|
454
761
|
}) {
|
|
455
|
-
const [pageIndex, setPageIndex] =
|
|
456
|
-
const [
|
|
457
|
-
const [
|
|
762
|
+
const [pageIndex, setPageIndex] = useState3(0);
|
|
763
|
+
const [pageSize, setPageSize] = useState3(20);
|
|
764
|
+
const [searchTerm, setSearchTerm] = useState3("");
|
|
765
|
+
const deferredSearch = useDeferredValue(searchTerm);
|
|
766
|
+
const [sort, setSort] = useState3(void 0);
|
|
458
767
|
const query = useQuery2({
|
|
459
|
-
queryKey: ["oon", "list", resourceKey, { pageIndex, searchTerm, sort }],
|
|
460
|
-
queryFn: () => resource.list({ pageIndex, pageSize
|
|
768
|
+
queryKey: ["oon", "list", resourceKey, { pageIndex, pageSize, searchTerm: deferredSearch, sort }],
|
|
769
|
+
queryFn: () => resource.list({ pageIndex, pageSize, searchTerm: deferredSearch || void 0, sort }),
|
|
461
770
|
placeholderData: keepPreviousData
|
|
462
771
|
});
|
|
463
772
|
const toggleSort = (field) => {
|
|
464
773
|
setPageIndex(0);
|
|
465
|
-
setSort((
|
|
466
|
-
if (
|
|
467
|
-
if (
|
|
774
|
+
setSort((previous) => {
|
|
775
|
+
if (previous === `${field}.asc`) return `${field}.desc`;
|
|
776
|
+
if (previous === `${field}.desc`) return void 0;
|
|
468
777
|
return `${field}.asc`;
|
|
469
778
|
});
|
|
470
779
|
};
|
|
471
780
|
const pagination = query.data?.pagination;
|
|
472
781
|
const rows = query.data?.results ?? [];
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
782
|
+
const totalItems = pagination?.totalItems ?? rows.length;
|
|
783
|
+
const totalPages = Math.max(1, pagination?.totalPages ?? 1);
|
|
784
|
+
const currentPage = Math.min(pageIndex + 1, totalPages);
|
|
785
|
+
const actionCount = onEdit || onDelete ? 1 : 0;
|
|
786
|
+
return /* @__PURE__ */ jsxs3(Box2, { bg: "white", borderWidth: "1px", borderColor: "#E5E9ED", borderRadius: "10px", boxShadow: "0 1px 2px rgba(15,23,42,.06)", overflow: "hidden", children: [
|
|
787
|
+
/* @__PURE__ */ jsxs3(Flex2, { minH: "60px", px: "14px", py: "12px", align: "center", gap: 4, borderBottomWidth: "1px", borderColor: "#E5E9ED", children: [
|
|
788
|
+
search ? /* @__PURE__ */ jsxs3(Flex2, { minW: { base: "100%", md: "min(420px, 55vw)" }, maxW: "420px", h: "35px", px: "10px", align: "center", gap: 2, color: "#98A2B3", bg: "white", borderWidth: "1px", borderColor: "#D5DDE3", borderRadius: "6px", _focusWithin: { borderColor: "#0F8DD0", boxShadow: "0 0 0 3px rgba(15,141,208,.10)" }, children: [
|
|
789
|
+
/* @__PURE__ */ jsx7(SearchIcon, {}),
|
|
790
|
+
/* @__PURE__ */ jsx7(
|
|
791
|
+
Input,
|
|
792
|
+
{
|
|
793
|
+
value: searchTerm,
|
|
794
|
+
onChange: (event) => {
|
|
795
|
+
setSearchTerm(event.target.value);
|
|
796
|
+
setPageIndex(0);
|
|
797
|
+
},
|
|
798
|
+
placeholder: "Pesquisar registros...",
|
|
799
|
+
h: "100%",
|
|
800
|
+
p: 0,
|
|
801
|
+
color: "#475467",
|
|
802
|
+
bg: "transparent",
|
|
803
|
+
border: 0,
|
|
804
|
+
borderRadius: 0,
|
|
805
|
+
fontSize: "11px",
|
|
806
|
+
_placeholder: { color: "#A6B0BC" },
|
|
807
|
+
_focusVisible: { boxShadow: "none", outline: "none" }
|
|
485
808
|
}
|
|
486
|
-
|
|
487
|
-
) : null,
|
|
488
|
-
/* @__PURE__ */
|
|
809
|
+
)
|
|
810
|
+
] }) : null,
|
|
811
|
+
/* @__PURE__ */ jsx7(Spacer, {}),
|
|
812
|
+
/* @__PURE__ */ jsxs3(Flex2, { align: "center", gap: 3, color: "#98A2B3", fontSize: "10px", children: [
|
|
813
|
+
query.isFetching ? /* @__PURE__ */ jsx7(Text3, { children: "Atualizando..." }) : null,
|
|
814
|
+
/* @__PURE__ */ jsxs3(Text3, { as: "strong", color: "#667085", fontWeight: "600", children: [
|
|
815
|
+
totalItems,
|
|
816
|
+
" registro",
|
|
817
|
+
totalItems === 1 ? "" : "s"
|
|
818
|
+
] })
|
|
819
|
+
] }),
|
|
489
820
|
toolbarExtra,
|
|
490
|
-
onCreate ? /* @__PURE__ */
|
|
821
|
+
onCreate ? /* @__PURE__ */ jsxs3(Button2, { minH: "35px", px: "13px", colorPalette: "brand", borderRadius: "6px", fontSize: "11px", fontWeight: "600", onClick: onCreate, children: [
|
|
822
|
+
/* @__PURE__ */ jsx7(PlusIcon, {}),
|
|
823
|
+
" Novo registro"
|
|
824
|
+
] }) : null
|
|
491
825
|
] }),
|
|
492
|
-
query.isLoading ? /* @__PURE__ */
|
|
493
|
-
/* @__PURE__ */
|
|
494
|
-
columns.map((
|
|
826
|
+
query.isLoading ? /* @__PURE__ */ jsx7(LoadingScreen, {}) : query.isError ? /* @__PURE__ */ jsx7(ErrorState, { error: query.error }) : /* @__PURE__ */ jsx7(Box2, { overflowX: "auto", children: /* @__PURE__ */ jsxs3(Table.Root, { size: "sm", minW: "900px", borderCollapse: "collapse", children: [
|
|
827
|
+
/* @__PURE__ */ jsx7(Table.Header, { children: /* @__PURE__ */ jsxs3(Table.Row, { bg: "#F4F7F9", children: [
|
|
828
|
+
columns.map((column) => /* @__PURE__ */ jsxs3(
|
|
495
829
|
Table.ColumnHeader,
|
|
496
830
|
{
|
|
497
|
-
|
|
498
|
-
|
|
831
|
+
h: "39px",
|
|
832
|
+
px: "10px",
|
|
833
|
+
py: "8px",
|
|
834
|
+
color: "#667085",
|
|
835
|
+
borderBottomWidth: "1px",
|
|
836
|
+
borderColor: "#D5DDE3",
|
|
837
|
+
fontSize: "10px",
|
|
838
|
+
fontWeight: "600",
|
|
839
|
+
letterSpacing: ".015em",
|
|
840
|
+
textTransform: "none",
|
|
841
|
+
whiteSpace: "nowrap",
|
|
842
|
+
cursor: column.sortable ? "pointer" : void 0,
|
|
843
|
+
onClick: column.sortable ? () => toggleSort(column.field) : void 0,
|
|
499
844
|
children: [
|
|
500
|
-
|
|
501
|
-
sort?.startsWith(`${
|
|
845
|
+
column.label ?? column.field,
|
|
846
|
+
sort?.startsWith(`${column.field}.`) ? sort.endsWith(".asc") ? " \u25B2" : " \u25BC" : ""
|
|
502
847
|
]
|
|
503
848
|
},
|
|
504
|
-
|
|
849
|
+
column.field
|
|
505
850
|
)),
|
|
506
|
-
|
|
851
|
+
actionCount ? /* @__PURE__ */ jsx7(Table.ColumnHeader, { w: "84px", h: "39px", px: "10px", py: "8px", color: "#667085", borderBottomWidth: "1px", borderColor: "#D5DDE3", fontSize: "10px", fontWeight: "600", textAlign: "center", children: "A\xE7\xF5es" }) : null
|
|
507
852
|
] }) }),
|
|
508
|
-
/* @__PURE__ */
|
|
509
|
-
columns.map((
|
|
510
|
-
|
|
511
|
-
onEdit ? /* @__PURE__ */
|
|
512
|
-
onDelete ? /* @__PURE__ */
|
|
853
|
+
/* @__PURE__ */ jsx7(Table.Body, { children: rows.length === 0 ? /* @__PURE__ */ jsx7(Table.Row, { children: /* @__PURE__ */ jsx7(Table.Cell, { colSpan: columns.length + actionCount, children: /* @__PURE__ */ jsx7(Text3, { minH: "140px", display: "grid", placeItems: "center", color: "#98A2B3", fontSize: "12px", children: "Nenhum registro encontrado." }) }) }) : rows.map((row, index) => /* @__PURE__ */ jsxs3(Table.Row, { bg: index % 2 ? "#FBFCFD" : "white", _hover: { bg: "#F0F7FF" }, children: [
|
|
854
|
+
columns.map((column) => /* @__PURE__ */ jsx7(Table.Cell, { maxW: "250px", h: "42px", px: "10px", py: "8px", overflow: "hidden", color: "#52606D", borderBottomWidth: "1px", borderColor: "#EEF1F3", fontSize: "11px", textOverflow: "ellipsis", whiteSpace: "nowrap", children: renderCell(row[column.field], column.kind) }, column.field)),
|
|
855
|
+
actionCount ? /* @__PURE__ */ jsx7(Table.Cell, { h: "42px", px: "10px", py: "5px", borderBottomWidth: "1px", borderColor: "#EEF1F3", textAlign: "center", children: /* @__PURE__ */ jsxs3(Flex2, { justify: "center", gap: "5px", children: [
|
|
856
|
+
onEdit ? /* @__PURE__ */ jsx7(Button2, { type: "button", "aria-label": "Editar", w: "31px", minW: "31px", h: "31px", p: 0, color: "#667085", bg: "white", borderWidth: "1px", borderColor: "#E5E9ED", borderRadius: "6px", onClick: () => onEdit(row), _hover: { color: "#036491", bg: "#F0F7FF", borderColor: "#B8D9EB" }, children: /* @__PURE__ */ jsx7(EditIcon, {}) }) : null,
|
|
857
|
+
onDelete ? /* @__PURE__ */ jsx7(Button2, { type: "button", "aria-label": "Excluir", w: "31px", minW: "31px", h: "31px", p: 0, color: "#667085", bg: "white", borderWidth: "1px", borderColor: "#E5E9ED", borderRadius: "6px", onClick: () => onDelete(row), _hover: { color: "#B42318", bg: "#FFF5F5", borderColor: "#FECACA" }, children: /* @__PURE__ */ jsx7(TrashIcon, {}) }) : null
|
|
513
858
|
] }) }) : null
|
|
514
|
-
] }, row._id ??
|
|
859
|
+
] }, row._id ?? index)) })
|
|
515
860
|
] }) }),
|
|
516
|
-
|
|
517
|
-
/* @__PURE__ */ jsxs3(
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
861
|
+
/* @__PURE__ */ jsxs3(Flex2, { minH: "56px", px: "14px", py: "10px", align: "center", justify: "flex-end", gap: "18px", color: "#7B8794", borderTopWidth: "1px", borderColor: "#E5E9ED", fontSize: "10px", flexWrap: "wrap", children: [
|
|
862
|
+
/* @__PURE__ */ jsxs3(Flex2, { align: "center", gap: 2, children: [
|
|
863
|
+
/* @__PURE__ */ jsx7(Text3, { children: "Linhas por p\xE1gina" }),
|
|
864
|
+
/* @__PURE__ */ jsxs3(NativeSelect.Root, { size: "xs", w: "68px", children: [
|
|
865
|
+
/* @__PURE__ */ jsx7(
|
|
866
|
+
NativeSelect.Field,
|
|
867
|
+
{
|
|
868
|
+
value: pageSize,
|
|
869
|
+
h: "30px",
|
|
870
|
+
pl: "8px",
|
|
871
|
+
pr: "26px",
|
|
872
|
+
color: "#52606D",
|
|
873
|
+
bg: "white",
|
|
874
|
+
borderColor: "#D5DDE3",
|
|
875
|
+
borderRadius: "5px",
|
|
876
|
+
fontSize: "10px",
|
|
877
|
+
onChange: (event) => {
|
|
878
|
+
setPageSize(Number(event.currentTarget.value));
|
|
879
|
+
setPageIndex(0);
|
|
880
|
+
},
|
|
881
|
+
children: [10, 20, 50, 100].map((size) => /* @__PURE__ */ jsx7("option", { value: size, children: size }, size))
|
|
882
|
+
}
|
|
883
|
+
),
|
|
884
|
+
/* @__PURE__ */ jsx7(NativeSelect.Indicator, {})
|
|
885
|
+
] })
|
|
886
|
+
] }),
|
|
887
|
+
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
888
|
+
"P\xE1gina ",
|
|
889
|
+
currentPage,
|
|
521
890
|
" de ",
|
|
522
|
-
|
|
891
|
+
totalPages
|
|
523
892
|
] }),
|
|
524
|
-
/* @__PURE__ */
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
|
893
|
+
/* @__PURE__ */ jsxs3(Flex2, { gap: "5px", children: [
|
|
894
|
+
/* @__PURE__ */ jsx7(Button2, { type: "button", "aria-label": "P\xE1gina anterior", w: "31px", minW: "31px", h: "31px", p: 0, color: "#667085", bg: "white", borderWidth: "1px", borderColor: "#E5E9ED", borderRadius: "6px", disabled: pageIndex <= 0, onClick: () => setPageIndex((current) => Math.max(0, current - 1)), _hover: { color: "#036491", bg: "#F0F7FF", borderColor: "#B8D9EB" }, children: /* @__PURE__ */ jsx7(ChevronIcon, { direction: "left" }) }),
|
|
895
|
+
/* @__PURE__ */ jsx7(Button2, { type: "button", "aria-label": "Pr\xF3xima p\xE1gina", w: "31px", minW: "31px", h: "31px", p: 0, color: "#667085", bg: "white", borderWidth: "1px", borderColor: "#E5E9ED", borderRadius: "6px", disabled: currentPage >= totalPages, onClick: () => setPageIndex((current) => Math.min(totalPages - 1, current + 1)), _hover: { color: "#036491", bg: "#F0F7FF", borderColor: "#B8D9EB" }, children: /* @__PURE__ */ jsx7(ChevronIcon, { direction: "right" }) })
|
|
896
|
+
] })
|
|
897
|
+
] })
|
|
537
898
|
] });
|
|
538
899
|
}
|
|
539
900
|
|
|
540
901
|
// src/components/internal/DynamicForm.tsx
|
|
541
|
-
import { useState as
|
|
542
|
-
import { Box as
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
902
|
+
import { useEffect as useEffect4, useState as useState5 } from "react";
|
|
903
|
+
import { Box as Box4, Button as Button4, Flex as Flex4, Heading as Heading2, Input as Input3, NativeSelect as NativeSelect2, SimpleGrid, Stack as Stack2, Text as Text5, Textarea } from "@chakra-ui/react";
|
|
904
|
+
|
|
905
|
+
// src/components/internal/ReferenceSelect.tsx
|
|
906
|
+
import { useEffect as useEffect3, useMemo as useMemo3, useRef, useState as useState4 } from "react";
|
|
907
|
+
import { Box as Box3, Button as Button3, Flex as Flex3, Input as Input2, Spinner as Spinner2, Stack, Text as Text4 } from "@chakra-ui/react";
|
|
908
|
+
import { useQuery as useQuery3 } from "@tanstack/react-query";
|
|
909
|
+
|
|
910
|
+
// src/components/internal/referenceUtils.ts
|
|
911
|
+
var COMMON_LABEL_FIELDS = [
|
|
912
|
+
"nome",
|
|
913
|
+
"razaoSocial",
|
|
914
|
+
"razao_social",
|
|
915
|
+
"descricao",
|
|
916
|
+
"numero",
|
|
917
|
+
"codigoIndividual",
|
|
918
|
+
"codigo_individual",
|
|
919
|
+
"codigo",
|
|
920
|
+
"label",
|
|
921
|
+
"email"
|
|
922
|
+
];
|
|
923
|
+
function primitiveText(value) {
|
|
924
|
+
if (value == null || value === "") return null;
|
|
925
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
926
|
+
return null;
|
|
927
|
+
}
|
|
928
|
+
function referenceOptionLabel(row, searchableFields = []) {
|
|
929
|
+
const ordered = [...COMMON_LABEL_FIELDS, ...searchableFields];
|
|
930
|
+
const seenFields = /* @__PURE__ */ new Set();
|
|
931
|
+
const seenValues = /* @__PURE__ */ new Set();
|
|
932
|
+
const values = [];
|
|
933
|
+
for (const field of ordered) {
|
|
934
|
+
if (seenFields.has(field)) continue;
|
|
935
|
+
seenFields.add(field);
|
|
936
|
+
const text = primitiveText(row[field]);
|
|
937
|
+
if (!text || seenValues.has(text)) continue;
|
|
938
|
+
seenValues.add(text);
|
|
939
|
+
values.push(text);
|
|
940
|
+
if (values.length === 2) break;
|
|
941
|
+
}
|
|
942
|
+
if (values.length) return values.join(" \u2014 ");
|
|
943
|
+
return primitiveText(row._id) ?? primitiveText(row.id) ?? "Registro sem identifica\xE7\xE3o";
|
|
944
|
+
}
|
|
945
|
+
function initialReferenceLabel(value) {
|
|
946
|
+
if (!value || typeof value !== "object") return "";
|
|
947
|
+
return referenceOptionLabel(value);
|
|
948
|
+
}
|
|
949
|
+
function referenceId(row) {
|
|
950
|
+
return String(row._id ?? row.id ?? "");
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// src/components/internal/ReferenceSelect.tsx
|
|
954
|
+
import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
955
|
+
function SearchIcon2() {
|
|
956
|
+
return /* @__PURE__ */ jsxs4("svg", { viewBox: "0 0 20 20", width: "15", height: "15", "aria-hidden": "true", children: [
|
|
957
|
+
/* @__PURE__ */ jsx8("circle", { cx: "8.5", cy: "8.5", r: "5.25", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
958
|
+
/* @__PURE__ */ jsx8("path", { d: "m12.4 12.4 4 4", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })
|
|
959
|
+
] });
|
|
960
|
+
}
|
|
961
|
+
function ChevronIcon2({ open }) {
|
|
962
|
+
return /* @__PURE__ */ jsx8(
|
|
963
|
+
"svg",
|
|
964
|
+
{
|
|
965
|
+
viewBox: "0 0 20 20",
|
|
966
|
+
width: "16",
|
|
967
|
+
height: "16",
|
|
968
|
+
"aria-hidden": "true",
|
|
969
|
+
style: { display: "block", transition: "transform .15s ease", transform: open ? "rotate(180deg)" : "rotate(0deg)" },
|
|
970
|
+
children: /* @__PURE__ */ jsx8("path", { d: "M5.5 7.5 10 12l4.5-4.5", fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
971
|
+
}
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
function ClearIcon() {
|
|
975
|
+
return /* @__PURE__ */ jsx8("svg", { viewBox: "0 0 20 20", width: "14", height: "14", "aria-hidden": "true", children: /* @__PURE__ */ jsx8("path", { d: "m6 6 8 8M14 6l-8 8", fill: "none", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round" }) });
|
|
976
|
+
}
|
|
977
|
+
function ReferenceSelect({
|
|
978
|
+
field,
|
|
979
|
+
inputId,
|
|
980
|
+
errorId,
|
|
981
|
+
label,
|
|
982
|
+
refModel,
|
|
983
|
+
value,
|
|
984
|
+
initialValue,
|
|
985
|
+
required,
|
|
986
|
+
invalid,
|
|
987
|
+
error,
|
|
988
|
+
onChange
|
|
989
|
+
}) {
|
|
990
|
+
const initialLabel = useMemo3(() => initialReferenceLabel(initialValue), [initialValue]);
|
|
991
|
+
const [selectedLabel, setSelectedLabel] = useState4(initialLabel);
|
|
992
|
+
const [search, setSearch] = useState4("");
|
|
993
|
+
const [debouncedSearch, setDebouncedSearch] = useState4("");
|
|
994
|
+
const [open, setOpen] = useState4(false);
|
|
995
|
+
const [activeIndex, setActiveIndex] = useState4(-1);
|
|
996
|
+
const rootRef = useRef(null);
|
|
997
|
+
const searchRef = useRef(null);
|
|
998
|
+
const optionRefs = useRef([]);
|
|
999
|
+
const schemaQuery = useModelSchema(refModel);
|
|
1000
|
+
const schema = schemaQuery.data;
|
|
1001
|
+
const basePath = schema?.basePath ?? `/${refModel.toLowerCase()}s`;
|
|
1002
|
+
const resource = useOonResource(basePath);
|
|
1003
|
+
useEffect3(() => {
|
|
1004
|
+
const timer = window.setTimeout(() => setDebouncedSearch(search.trim()), 250);
|
|
1005
|
+
return () => window.clearTimeout(timer);
|
|
1006
|
+
}, [search]);
|
|
1007
|
+
useEffect3(() => {
|
|
1008
|
+
if (!value) setSelectedLabel("");
|
|
1009
|
+
else if (initialLabel) setSelectedLabel(initialLabel);
|
|
1010
|
+
}, [initialLabel, value]);
|
|
1011
|
+
useEffect3(() => {
|
|
1012
|
+
if (!open) return;
|
|
1013
|
+
const onPointerDown = (event) => {
|
|
1014
|
+
if (!rootRef.current?.contains(event.target)) {
|
|
1015
|
+
setOpen(false);
|
|
1016
|
+
setActiveIndex(-1);
|
|
1017
|
+
}
|
|
1018
|
+
};
|
|
1019
|
+
document.addEventListener("pointerdown", onPointerDown);
|
|
1020
|
+
return () => document.removeEventListener("pointerdown", onPointerDown);
|
|
1021
|
+
}, [open]);
|
|
1022
|
+
useEffect3(() => {
|
|
1023
|
+
if (!open) return;
|
|
1024
|
+
window.setTimeout(() => searchRef.current?.focus(), 0);
|
|
1025
|
+
}, [open]);
|
|
1026
|
+
const listQuery = useQuery3({
|
|
1027
|
+
queryKey: ["oon", "reference-options", refModel, basePath, debouncedSearch],
|
|
1028
|
+
queryFn: () => resource.list({ pageIndex: 0, pageSize: 30, searchTerm: debouncedSearch || void 0 }),
|
|
1029
|
+
enabled: !!schema && open,
|
|
1030
|
+
staleTime: 3e4
|
|
1031
|
+
});
|
|
1032
|
+
const currentQuery = useQuery3({
|
|
1033
|
+
queryKey: ["oon", "reference-current", refModel, basePath, value],
|
|
1034
|
+
queryFn: () => resource.get(value),
|
|
1035
|
+
enabled: !!schema && !!value && !selectedLabel,
|
|
1036
|
+
staleTime: 5 * 6e4
|
|
1037
|
+
});
|
|
1038
|
+
useEffect3(() => {
|
|
1039
|
+
if (!currentQuery.data) return;
|
|
1040
|
+
setSelectedLabel(referenceOptionLabel(currentQuery.data, schema?.searchable));
|
|
1041
|
+
}, [currentQuery.data, schema?.searchable]);
|
|
1042
|
+
const items = listQuery.data?.results ?? [];
|
|
1043
|
+
const listId = `oon-reference-${field}`;
|
|
1044
|
+
const resolvedInputId = inputId ?? `oon-reference-input-${field}`;
|
|
1045
|
+
const activeOptionId = activeIndex >= 0 ? `${listId}-option-${activeIndex}` : void 0;
|
|
1046
|
+
const openList = (seed = "") => {
|
|
1047
|
+
if (seed) setSearch(seed);
|
|
1048
|
+
setOpen(true);
|
|
1049
|
+
setActiveIndex((current) => current >= 0 ? current : 0);
|
|
1050
|
+
};
|
|
1051
|
+
const closeList = () => {
|
|
1052
|
+
setOpen(false);
|
|
1053
|
+
setActiveIndex(-1);
|
|
1054
|
+
};
|
|
1055
|
+
const choose = (record) => {
|
|
1056
|
+
const id = referenceId(record);
|
|
1057
|
+
if (!id) return;
|
|
1058
|
+
onChange(id);
|
|
1059
|
+
setSelectedLabel(referenceOptionLabel(record, schema?.searchable));
|
|
1060
|
+
setSearch("");
|
|
1061
|
+
setDebouncedSearch("");
|
|
1062
|
+
closeList();
|
|
1063
|
+
};
|
|
1064
|
+
const clear = (event) => {
|
|
1065
|
+
event.preventDefault();
|
|
1066
|
+
event.stopPropagation();
|
|
1067
|
+
onChange("");
|
|
1068
|
+
setSelectedLabel("");
|
|
1069
|
+
setSearch("");
|
|
1070
|
+
setDebouncedSearch("");
|
|
1071
|
+
};
|
|
1072
|
+
const moveActive = (direction) => {
|
|
1073
|
+
if (!items.length) return;
|
|
1074
|
+
setActiveIndex((current) => {
|
|
1075
|
+
const start2 = current < 0 ? direction === 1 ? -1 : 0 : current;
|
|
1076
|
+
const next = (start2 + direction + items.length) % items.length;
|
|
1077
|
+
window.setTimeout(() => optionRefs.current[next]?.scrollIntoView({ block: "nearest" }), 0);
|
|
1078
|
+
return next;
|
|
1079
|
+
});
|
|
1080
|
+
};
|
|
1081
|
+
return /* @__PURE__ */ jsxs4(Box3, { ref: rootRef, position: "relative", children: [
|
|
1082
|
+
/* @__PURE__ */ jsxs4(
|
|
1083
|
+
Button3,
|
|
1084
|
+
{
|
|
1085
|
+
id: resolvedInputId,
|
|
1086
|
+
type: "button",
|
|
1087
|
+
variant: "outline",
|
|
1088
|
+
w: "100%",
|
|
1089
|
+
minH: "37px",
|
|
1090
|
+
h: "auto",
|
|
1091
|
+
px: "10px",
|
|
1092
|
+
py: "8px",
|
|
1093
|
+
justifyContent: "space-between",
|
|
1094
|
+
gap: 2,
|
|
1095
|
+
color: "#475467",
|
|
1096
|
+
bg: "white",
|
|
1097
|
+
borderColor: invalid ? "#DC2626" : "#D5DDE3",
|
|
1098
|
+
borderRadius: "6px",
|
|
1099
|
+
boxShadow: invalid ? "0 0 0 3px rgba(220,38,38,.08)" : void 0,
|
|
1100
|
+
fontSize: "11px",
|
|
1101
|
+
fontWeight: "400",
|
|
1102
|
+
textAlign: "left",
|
|
1103
|
+
"aria-haspopup": "listbox",
|
|
1104
|
+
"aria-expanded": open,
|
|
1105
|
+
"aria-controls": listId,
|
|
1106
|
+
"aria-activedescendant": open ? activeOptionId : void 0,
|
|
1107
|
+
"aria-required": required,
|
|
1108
|
+
"aria-invalid": invalid || void 0,
|
|
1109
|
+
"aria-describedby": error ? errorId : void 0,
|
|
1110
|
+
onClick: () => open ? closeList() : openList(),
|
|
1111
|
+
onKeyDown: (event) => {
|
|
1112
|
+
if (event.key === "ArrowDown") {
|
|
1113
|
+
event.preventDefault();
|
|
1114
|
+
if (!open) openList();
|
|
1115
|
+
else moveActive(1);
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1118
|
+
if (event.key === "ArrowUp") {
|
|
1119
|
+
event.preventDefault();
|
|
1120
|
+
if (!open) openList();
|
|
1121
|
+
else moveActive(-1);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
1124
|
+
if (event.key === "Enter" && open && activeIndex >= 0 && items[activeIndex]) {
|
|
1125
|
+
event.preventDefault();
|
|
1126
|
+
choose(items[activeIndex]);
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
if (event.key === "Escape" && open) {
|
|
1130
|
+
event.preventDefault();
|
|
1131
|
+
event.stopPropagation();
|
|
1132
|
+
closeList();
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
if (!event.ctrlKey && !event.metaKey && !event.altKey && event.key.length === 1) {
|
|
1136
|
+
event.preventDefault();
|
|
1137
|
+
openList(event.key);
|
|
1138
|
+
}
|
|
1139
|
+
},
|
|
1140
|
+
_hover: { bg: "white", borderColor: "#B9C7D1" },
|
|
1141
|
+
_focusVisible: { borderColor: invalid ? "#DC2626" : "#0F8DD0", boxShadow: invalid ? "0 0 0 3px rgba(220,38,38,.08)" : "0 0 0 3px rgba(15,141,208,.10)" },
|
|
1142
|
+
children: [
|
|
1143
|
+
/* @__PURE__ */ jsx8(Text4, { as: "span", minW: 0, flex: "1", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: selectedLabel || value ? "#475467" : "#98A2B3", children: selectedLabel || (value ? String(value) : "Selecione ou digite para pesquisar...") }),
|
|
1144
|
+
/* @__PURE__ */ jsxs4(Flex3, { as: "span", align: "center", gap: 1, flex: "0 0 auto", color: "#98A2B3", children: [
|
|
1145
|
+
value ? /* @__PURE__ */ jsx8(
|
|
1146
|
+
Box3,
|
|
1147
|
+
{
|
|
1148
|
+
as: "span",
|
|
1149
|
+
role: "button",
|
|
1150
|
+
tabIndex: 0,
|
|
1151
|
+
"aria-label": `Limpar ${label}`,
|
|
1152
|
+
w: "20px",
|
|
1153
|
+
h: "20px",
|
|
1154
|
+
display: "grid",
|
|
1155
|
+
placeItems: "center",
|
|
1156
|
+
borderRadius: "4px",
|
|
1157
|
+
onClick: clear,
|
|
1158
|
+
onKeyDown: (event) => {
|
|
1159
|
+
if (event.key === "Enter" || event.key === " ") clear(event);
|
|
1160
|
+
},
|
|
1161
|
+
_hover: { color: "#B42318", bg: "#FFF1F1" },
|
|
1162
|
+
children: /* @__PURE__ */ jsx8(ClearIcon, {})
|
|
1163
|
+
}
|
|
1164
|
+
) : null,
|
|
1165
|
+
/* @__PURE__ */ jsx8(ChevronIcon2, { open })
|
|
1166
|
+
] })
|
|
1167
|
+
]
|
|
1168
|
+
}
|
|
1169
|
+
),
|
|
1170
|
+
open ? /* @__PURE__ */ jsxs4(
|
|
1171
|
+
Box3,
|
|
1172
|
+
{
|
|
1173
|
+
position: "absolute",
|
|
1174
|
+
zIndex: 120,
|
|
1175
|
+
top: "calc(100% + 5px)",
|
|
1176
|
+
left: 0,
|
|
1177
|
+
right: 0,
|
|
1178
|
+
overflow: "hidden",
|
|
1179
|
+
bg: "white",
|
|
1180
|
+
borderWidth: "1px",
|
|
1181
|
+
borderColor: "#D5DDE3",
|
|
1182
|
+
borderRadius: "8px",
|
|
1183
|
+
boxShadow: "0 10px 30px rgba(15,23,42,.10)",
|
|
1184
|
+
children: [
|
|
1185
|
+
/* @__PURE__ */ jsxs4(Flex3, { h: "38px", px: "10px", align: "center", gap: "7px", color: "#98A2B3", borderBottomWidth: "1px", borderColor: "#E5E9ED", children: [
|
|
1186
|
+
/* @__PURE__ */ jsx8(SearchIcon2, {}),
|
|
1187
|
+
/* @__PURE__ */ jsx8(
|
|
1188
|
+
Input2,
|
|
1189
|
+
{
|
|
1190
|
+
ref: searchRef,
|
|
1191
|
+
value: search,
|
|
1192
|
+
onChange: (event) => {
|
|
1193
|
+
setSearch(event.target.value);
|
|
1194
|
+
setActiveIndex(0);
|
|
1195
|
+
},
|
|
1196
|
+
placeholder: "Digite para filtrar...",
|
|
1197
|
+
h: "100%",
|
|
1198
|
+
p: 0,
|
|
1199
|
+
color: "#475467",
|
|
1200
|
+
bg: "transparent",
|
|
1201
|
+
border: 0,
|
|
1202
|
+
borderRadius: 0,
|
|
1203
|
+
fontSize: "11px",
|
|
1204
|
+
_focusVisible: { boxShadow: "none", outline: "none" },
|
|
1205
|
+
onKeyDown: (event) => {
|
|
1206
|
+
if (event.key === "ArrowDown") {
|
|
1207
|
+
event.preventDefault();
|
|
1208
|
+
moveActive(1);
|
|
1209
|
+
} else if (event.key === "ArrowUp") {
|
|
1210
|
+
event.preventDefault();
|
|
1211
|
+
moveActive(-1);
|
|
1212
|
+
} else if (event.key === "Enter" && activeIndex >= 0 && items[activeIndex]) {
|
|
1213
|
+
event.preventDefault();
|
|
1214
|
+
choose(items[activeIndex]);
|
|
1215
|
+
} else if (event.key === "Escape") {
|
|
1216
|
+
event.preventDefault();
|
|
1217
|
+
event.stopPropagation();
|
|
1218
|
+
closeList();
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
)
|
|
1223
|
+
] }),
|
|
1224
|
+
/* @__PURE__ */ jsx8(Stack, { id: listId, role: "listbox", gap: 0, maxH: "230px", p: "5px", overflowY: "auto", children: schemaQuery.isLoading || listQuery.isLoading ? /* @__PURE__ */ jsxs4(Flex3, { minH: "54px", align: "center", justify: "center", gap: 2, color: "#98A2B3", fontSize: "10px", children: [
|
|
1225
|
+
/* @__PURE__ */ jsx8(Spinner2, { size: "xs" }),
|
|
1226
|
+
" Carregando..."
|
|
1227
|
+
] }) : schemaQuery.isError || listQuery.isError ? /* @__PURE__ */ jsx8(Text4, { py: "18px", px: "10px", color: "#B42318", fontSize: "10px", textAlign: "center", children: "N\xE3o foi poss\xEDvel carregar as op\xE7\xF5es." }) : items.length === 0 ? /* @__PURE__ */ jsx8(Text4, { py: "18px", px: "10px", color: "#98A2B3", fontSize: "10px", textAlign: "center", children: "Nenhum registro encontrado." }) : items.map((record, index) => {
|
|
1228
|
+
const id = referenceId(record);
|
|
1229
|
+
if (!id) return null;
|
|
1230
|
+
const selected = String(id) === String(value);
|
|
1231
|
+
const active = index === activeIndex;
|
|
1232
|
+
const optionLabel = referenceOptionLabel(record, schema?.searchable);
|
|
1233
|
+
const status = record.status;
|
|
1234
|
+
return /* @__PURE__ */ jsxs4(
|
|
1235
|
+
Button3,
|
|
1236
|
+
{
|
|
1237
|
+
id: `${listId}-option-${index}`,
|
|
1238
|
+
ref: (element) => {
|
|
1239
|
+
optionRefs.current[index] = element;
|
|
1240
|
+
},
|
|
1241
|
+
type: "button",
|
|
1242
|
+
role: "option",
|
|
1243
|
+
"aria-selected": selected,
|
|
1244
|
+
w: "100%",
|
|
1245
|
+
minH: "38px",
|
|
1246
|
+
h: "auto",
|
|
1247
|
+
px: "9px",
|
|
1248
|
+
py: "7px",
|
|
1249
|
+
display: "flex",
|
|
1250
|
+
flexDirection: "column",
|
|
1251
|
+
alignItems: "flex-start",
|
|
1252
|
+
gap: 0,
|
|
1253
|
+
color: active || selected ? "#014364" : "#475467",
|
|
1254
|
+
bg: active || selected ? "#F0F7FF" : "transparent",
|
|
1255
|
+
border: 0,
|
|
1256
|
+
borderRadius: "5px",
|
|
1257
|
+
fontWeight: "400",
|
|
1258
|
+
textAlign: "left",
|
|
1259
|
+
onMouseEnter: () => setActiveIndex(index),
|
|
1260
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
1261
|
+
onClick: () => choose(record),
|
|
1262
|
+
_hover: { color: "#014364", bg: "#F0F7FF" },
|
|
1263
|
+
children: [
|
|
1264
|
+
/* @__PURE__ */ jsx8(Text4, { as: "strong", fontSize: "10px", fontWeight: "600", children: optionLabel }),
|
|
1265
|
+
status ? /* @__PURE__ */ jsx8(Text4, { as: "small", color: "#98A2B3", fontSize: "9px", textTransform: "capitalize", children: String(status).replace(/_/g, " ") }) : null
|
|
1266
|
+
]
|
|
1267
|
+
},
|
|
1268
|
+
id
|
|
1269
|
+
);
|
|
1270
|
+
}) })
|
|
1271
|
+
]
|
|
1272
|
+
}
|
|
1273
|
+
) : null
|
|
1274
|
+
] });
|
|
549
1275
|
}
|
|
550
|
-
|
|
1276
|
+
|
|
1277
|
+
// src/components/internal/DynamicForm.tsx
|
|
1278
|
+
import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1279
|
+
var idFor = (field) => `oon-form-field-${field.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
|
|
1280
|
+
var errorIdFor = (field) => `${idFor(field)}-error`;
|
|
1281
|
+
var normalize = (value) => String(value ?? "").normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
|
1282
|
+
function CloseIcon() {
|
|
1283
|
+
return /* @__PURE__ */ jsx9("svg", { viewBox: "0 0 20 20", width: "18", height: "18", "aria-hidden": "true", children: /* @__PURE__ */ jsx9("path", { d: "m6 6 8 8M14 6l-8 8", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }) });
|
|
1284
|
+
}
|
|
1285
|
+
function CheckIcon() {
|
|
1286
|
+
return /* @__PURE__ */ jsxs5("svg", { viewBox: "0 0 20 20", width: "16", height: "16", "aria-hidden": "true", children: [
|
|
1287
|
+
/* @__PURE__ */ jsx9("circle", { cx: "10", cy: "10", r: "7", fill: "none", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
1288
|
+
/* @__PURE__ */ jsx9("path", { d: "m6.8 10.1 2 2 4.3-4.4", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" })
|
|
1289
|
+
] });
|
|
1290
|
+
}
|
|
1291
|
+
function AlertIcon() {
|
|
1292
|
+
return /* @__PURE__ */ jsxs5("svg", { viewBox: "0 0 20 20", width: "17", height: "17", "aria-hidden": "true", children: [
|
|
1293
|
+
/* @__PURE__ */ jsx9("circle", { cx: "10", cy: "10", r: "7", fill: "none", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
1294
|
+
/* @__PURE__ */ jsx9("path", { d: "M10 6.5v4.2M10 13.5v.1", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" })
|
|
1295
|
+
] });
|
|
1296
|
+
}
|
|
1297
|
+
function toValue(value, kind) {
|
|
551
1298
|
if (value == null) return "";
|
|
552
|
-
if (kind === "date"
|
|
1299
|
+
if (kind === "date") {
|
|
553
1300
|
try {
|
|
554
1301
|
return new Date(value).toISOString().slice(0, 10);
|
|
555
1302
|
} catch {
|
|
@@ -557,101 +1304,221 @@ function toInputValue(value, kind) {
|
|
|
557
1304
|
}
|
|
558
1305
|
}
|
|
559
1306
|
if (typeof value === "object") {
|
|
560
|
-
const
|
|
561
|
-
return String(
|
|
1307
|
+
const record = value;
|
|
1308
|
+
return String(record._id ?? record.id ?? "");
|
|
562
1309
|
}
|
|
563
1310
|
return String(value);
|
|
564
1311
|
}
|
|
565
|
-
function
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
1312
|
+
function coerce(value, kind) {
|
|
1313
|
+
if (value === "") return void 0;
|
|
1314
|
+
if (["number", "currency", "currencyConverted"].includes(kind ?? "")) return Number(value);
|
|
1315
|
+
if (kind === "boolean") return value === "true";
|
|
1316
|
+
return value;
|
|
1317
|
+
}
|
|
1318
|
+
function validate(fields, values) {
|
|
1319
|
+
const errors = {};
|
|
1320
|
+
fields.forEach((field) => {
|
|
1321
|
+
if (field.required && !(values[field.field] ?? "").trim()) errors[field.field] = `${fieldLabel({ name: field.field, label: field.label })} \xE9 obrigat\xF3rio.`;
|
|
570
1322
|
});
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
1323
|
+
return errors;
|
|
1324
|
+
}
|
|
1325
|
+
function messageOf(error) {
|
|
1326
|
+
if (!error) return null;
|
|
1327
|
+
if (typeof error === "string") return error;
|
|
1328
|
+
if (error instanceof Error) return error.message;
|
|
1329
|
+
if (typeof error === "object") {
|
|
1330
|
+
const record = error;
|
|
1331
|
+
if (typeof record.message === "string") return record.message;
|
|
1332
|
+
if (record.error && typeof record.error === "object" && typeof record.error.message === "string") return record.error.message;
|
|
1333
|
+
}
|
|
1334
|
+
return "N\xE3o foi poss\xEDvel salvar o registro.";
|
|
1335
|
+
}
|
|
1336
|
+
function detailErrors(error, fields) {
|
|
1337
|
+
const result = {};
|
|
1338
|
+
const lookup = new Map(fields.flatMap((field) => [[normalize(field.field), field], [normalize(field.label), field]]));
|
|
1339
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
1340
|
+
const visit = (value, hint) => {
|
|
1341
|
+
if (value == null) return;
|
|
1342
|
+
if (Array.isArray(value)) return value.forEach((item) => visit(item, hint));
|
|
1343
|
+
if (typeof value !== "object" || seen.has(value)) return;
|
|
1344
|
+
seen.add(value);
|
|
1345
|
+
const record = value;
|
|
1346
|
+
const candidate = String(record.field ?? record.path ?? record.property ?? record.name ?? hint ?? "");
|
|
1347
|
+
const field = lookup.get(normalize(candidate.split(/[.[\]]+/).filter(Boolean).pop() ?? candidate));
|
|
1348
|
+
const raw = record.message ?? record.msg ?? record.reason;
|
|
1349
|
+
if (field && typeof raw === "string") result[field.field] = /required|obrigat/i.test(raw) ? `${fieldLabel({ name: field.field, label: field.label })} \xE9 obrigat\xF3rio.` : raw;
|
|
1350
|
+
Object.entries(record).forEach(([key, nested]) => {
|
|
1351
|
+
const direct = lookup.get(normalize(key));
|
|
1352
|
+
if (direct && typeof nested === "string") result[direct.field] = nested;
|
|
1353
|
+
visit(nested, key);
|
|
1354
|
+
});
|
|
1355
|
+
};
|
|
1356
|
+
if (error && typeof error === "object") {
|
|
1357
|
+
const record = error;
|
|
1358
|
+
visit(record.details ?? record.error?.details ?? record);
|
|
1359
|
+
}
|
|
1360
|
+
return result;
|
|
1361
|
+
}
|
|
1362
|
+
function focusField(field) {
|
|
1363
|
+
window.setTimeout(() => {
|
|
1364
|
+
const element = document.getElementById(idFor(field));
|
|
1365
|
+
element?.scrollIntoView?.({ behavior: "smooth", block: "center" });
|
|
1366
|
+
element?.focus({ preventScroll: true });
|
|
1367
|
+
}, 0);
|
|
1368
|
+
}
|
|
1369
|
+
function isMultiline(field) {
|
|
1370
|
+
return field.multiline === true || /observa|descricao|mensagem|detalhe|necessidade/i.test(field.field);
|
|
1371
|
+
}
|
|
1372
|
+
function Label({ id, label, required }) {
|
|
1373
|
+
return /* @__PURE__ */ jsx9("label", { htmlFor: id, children: /* @__PURE__ */ jsxs5(Text5, { as: "span", display: "block", mb: "6px", color: "#475467", fontSize: "11px", fontWeight: "600", children: [
|
|
1374
|
+
label,
|
|
1375
|
+
required ? /* @__PURE__ */ jsx9(Text5, { as: "span", color: "#DC2626", children: " *" }) : null
|
|
1376
|
+
] }) });
|
|
1377
|
+
}
|
|
1378
|
+
function ErrorText({ id, text }) {
|
|
1379
|
+
return text ? /* @__PURE__ */ jsx9(Text5, { id, role: "alert", mt: "5px", color: "#DC2626", fontSize: "10px", fontWeight: "500", children: text }) : null;
|
|
1380
|
+
}
|
|
1381
|
+
var controlProps = (invalid) => ({
|
|
1382
|
+
minH: "37px",
|
|
1383
|
+
px: "10px",
|
|
1384
|
+
py: "8px",
|
|
1385
|
+
color: "#475467",
|
|
1386
|
+
bg: "white",
|
|
1387
|
+
borderColor: invalid ? "#DC2626" : "#D5DDE3",
|
|
1388
|
+
borderRadius: "6px",
|
|
1389
|
+
boxShadow: invalid ? "0 0 0 3px rgba(220,38,38,.08)" : void 0,
|
|
1390
|
+
fontSize: "11px"
|
|
1391
|
+
});
|
|
1392
|
+
function DynamicForm({ title, eyebrow = "Cadastro", fields, initialValues, submitting, error, onSubmit, onCancel, onFieldChange }) {
|
|
1393
|
+
const [values, setValues] = useState5(() => Object.fromEntries(fields.map((field) => [field.field, toValue(initialValues?.[field.field], field.kind)])));
|
|
1394
|
+
const [errors, setErrors] = useState5({});
|
|
1395
|
+
const [dismissed, setDismissed] = useState5(false);
|
|
1396
|
+
useEffect4(() => {
|
|
1397
|
+
const listener = (event) => {
|
|
1398
|
+
if (event.key !== "Escape" || event.defaultPrevented || submitting) return;
|
|
1399
|
+
if (event.target?.closest('[role="combobox"], [role="listbox"]')) return;
|
|
1400
|
+
onCancel();
|
|
1401
|
+
};
|
|
1402
|
+
document.addEventListener("keydown", listener);
|
|
1403
|
+
return () => document.removeEventListener("keydown", listener);
|
|
1404
|
+
}, [onCancel, submitting]);
|
|
1405
|
+
useEffect4(() => {
|
|
1406
|
+
setDismissed(false);
|
|
1407
|
+
if (!error) return;
|
|
1408
|
+
const detailed = detailErrors(error, fields);
|
|
1409
|
+
const fallback = /validacao|validation/i.test(normalize(messageOf(error))) ? validate(fields, values) : {};
|
|
1410
|
+
const next = Object.keys(detailed).length ? detailed : fallback;
|
|
1411
|
+
if (!Object.keys(next).length) return;
|
|
1412
|
+
setErrors((current) => ({ ...current, ...next }));
|
|
1413
|
+
const first = fields.find((field) => next[field.field]);
|
|
1414
|
+
if (first) focusField(first.field);
|
|
1415
|
+
}, [error]);
|
|
1416
|
+
const change = (field, value) => {
|
|
1417
|
+
setValues((current) => ({ ...current, [field]: value }));
|
|
1418
|
+
setErrors((current) => {
|
|
1419
|
+
if (!current[field]) return current;
|
|
1420
|
+
const next = { ...current };
|
|
1421
|
+
delete next[field];
|
|
1422
|
+
return next;
|
|
1423
|
+
});
|
|
1424
|
+
setDismissed(true);
|
|
1425
|
+
onFieldChange?.();
|
|
1426
|
+
};
|
|
1427
|
+
const submit = (event) => {
|
|
1428
|
+
event.preventDefault();
|
|
1429
|
+
const validation = validate(fields, values);
|
|
1430
|
+
if (Object.keys(validation).length) {
|
|
1431
|
+
setErrors(validation);
|
|
1432
|
+
setDismissed(true);
|
|
1433
|
+
const first = fields.find((field) => validation[field.field]);
|
|
1434
|
+
if (first) focusField(first.field);
|
|
1435
|
+
return;
|
|
578
1436
|
}
|
|
1437
|
+
const payload = {};
|
|
1438
|
+
fields.forEach((field) => {
|
|
1439
|
+
const value = coerce(values[field.field] ?? "", field.kind);
|
|
1440
|
+
if (value !== void 0) payload[field.field] = value;
|
|
1441
|
+
});
|
|
1442
|
+
setErrors({});
|
|
579
1443
|
onSubmit(payload);
|
|
580
1444
|
};
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
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 ? " *" : ""
|
|
1445
|
+
const invalid = fields.filter((field) => errors[field.field]);
|
|
1446
|
+
const generic = dismissed ? null : messageOf(error);
|
|
1447
|
+
return /* @__PURE__ */ jsx9(Flex4, { position: "fixed", inset: 0, zIndex: 1e3, align: "center", justify: "center", p: { base: 2, md: 6 }, bg: "rgba(15,23,42,.48)", backdropFilter: "blur(2px)", onMouseDown: (event) => {
|
|
1448
|
+
if (event.currentTarget === event.target && !submitting) onCancel();
|
|
1449
|
+
}, children: /* @__PURE__ */ jsxs5(Box4, { role: "dialog", "aria-modal": "true", "aria-label": title, w: "min(860px, 96vw)", maxH: "min(880px, 94vh)", overflow: "hidden", bg: "white", borderWidth: "1px", borderColor: "#E5E9ED", borderRadius: "14px", boxShadow: "0 24px 70px rgba(15,23,42,.24)", children: [
|
|
1450
|
+
/* @__PURE__ */ jsxs5(Flex4, { minH: "76px", px: "20px", py: "16px", align: "center", justify: "space-between", borderBottomWidth: "1px", borderColor: "#E5E9ED", children: [
|
|
1451
|
+
/* @__PURE__ */ jsxs5(Box4, { children: [
|
|
1452
|
+
/* @__PURE__ */ jsx9(Text5, { color: "#0474AF", fontSize: "10px", fontWeight: "600", letterSpacing: ".07em", textTransform: "uppercase", children: eyebrow }),
|
|
1453
|
+
/* @__PURE__ */ jsx9(Heading2, { mt: "3px", color: "#344054", fontSize: "18px", fontWeight: "600", children: title })
|
|
1454
|
+
] }),
|
|
1455
|
+
/* @__PURE__ */ jsx9(Button4, { type: "button", "aria-label": "Fechar", w: "31px", minW: "31px", h: "31px", p: 0, color: "#667085", bg: "white", borderWidth: "1px", borderColor: "#E5E9ED", borderRadius: "6px", disabled: submitting, onClick: onCancel, _hover: { color: "#036491", bg: "#F0F7FF", borderColor: "#B8D9EB" }, children: /* @__PURE__ */ jsx9(CloseIcon, {}) })
|
|
1456
|
+
] }),
|
|
1457
|
+
/* @__PURE__ */ jsxs5("form", { noValidate: true, onSubmit: submit, children: [
|
|
1458
|
+
/* @__PURE__ */ jsx9(Box4, { maxH: "calc(94vh - 148px)", px: "20px", pt: "18px", pb: "24px", overflowY: "auto", children: /* @__PURE__ */ jsxs5(Stack2, { gap: 4, children: [
|
|
1459
|
+
invalid.length ? /* @__PURE__ */ jsxs5(Box4, { role: "alert", p: "11px 13px", color: "#9B1C1C", bg: "#FFF5F5", borderWidth: "1px", borderColor: "#FECACA", borderLeftWidth: "4px", borderLeftColor: "#DC2626", borderRadius: "8px", fontSize: "11px", children: [
|
|
1460
|
+
/* @__PURE__ */ jsxs5(Flex4, { align: "center", gap: "7px", children: [
|
|
1461
|
+
/* @__PURE__ */ jsx9(AlertIcon, {}),
|
|
1462
|
+
/* @__PURE__ */ jsx9(Text5, { as: "strong", children: "N\xE3o foi poss\xEDvel salvar. Corrija os campos abaixo:" })
|
|
630
1463
|
] }),
|
|
631
|
-
/* @__PURE__ */
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
1464
|
+
/* @__PURE__ */ jsx9(Box4, { as: "ul", mt: "7px", mb: 0, pl: "20px", display: "grid", gap: "3px", children: invalid.map((field) => /* @__PURE__ */ jsx9("li", { children: /* @__PURE__ */ jsxs5(Button4, { type: "button", variant: "plain", minH: "auto", h: "auto", p: 0, color: "#B42318", fontSize: "10px", fontWeight: "400", textDecoration: "underline", onClick: () => focusField(field.field), children: [
|
|
1465
|
+
fieldLabel({ name: field.field, label: field.label }),
|
|
1466
|
+
": ",
|
|
1467
|
+
errors[field.field]
|
|
1468
|
+
] }) }, field.field)) })
|
|
1469
|
+
] }) : null,
|
|
1470
|
+
generic && !invalid.length ? /* @__PURE__ */ jsxs5(Flex4, { role: "alert", p: "10px 12px", align: "center", gap: 2, color: "#B42318", bg: "#FEF3F2", borderWidth: "1px", borderColor: "#FECDCA", borderRadius: "7px", fontSize: "11px", children: [
|
|
1471
|
+
/* @__PURE__ */ jsx9(AlertIcon, {}),
|
|
1472
|
+
generic
|
|
1473
|
+
] }) : null,
|
|
1474
|
+
/* @__PURE__ */ jsx9(SimpleGrid, { columns: { base: 1, lg: 2 }, gapX: "16px", gapY: "15px", children: fields.map((baseField) => {
|
|
1475
|
+
const field = baseField;
|
|
1476
|
+
const label = fieldLabel({ name: field.field, label: field.label });
|
|
1477
|
+
const message = errors[field.field];
|
|
1478
|
+
const id = idFor(field.field);
|
|
1479
|
+
const errorId = message ? errorIdFor(field.field) : void 0;
|
|
1480
|
+
const multiline = isMultiline(field);
|
|
1481
|
+
const span = field.span === 2 || multiline;
|
|
1482
|
+
const wrap = (control) => /* @__PURE__ */ jsxs5(Box4, { gridColumn: span ? "1 / -1" : void 0, children: [
|
|
1483
|
+
/* @__PURE__ */ jsx9(Label, { id, label, required: field.required }),
|
|
1484
|
+
control,
|
|
1485
|
+
/* @__PURE__ */ jsx9(ErrorText, { id: errorId, text: message })
|
|
1486
|
+
] }, field.field);
|
|
1487
|
+
if (field.kind === "ref" && field.ref) return wrap(/* @__PURE__ */ jsx9(ReferenceSelect, { field: field.field, inputId: id, errorId, label, refModel: field.ref, value: values[field.field] ?? "", initialValue: initialValues?.[field.field], required: field.required, invalid: !!message, error: message, onChange: (value) => change(field.field, value) }));
|
|
1488
|
+
if (field.kind === "enum" && field.options?.length) return wrap(/* @__PURE__ */ jsxs5(NativeSelect2.Root, { size: "sm", children: [
|
|
1489
|
+
/* @__PURE__ */ jsxs5(NativeSelect2.Field, { id, "aria-invalid": !!message, "aria-describedby": errorId, value: values[field.field] ?? "", ...controlProps(!!message), onChange: (event) => change(field.field, event.currentTarget.value), children: [
|
|
1490
|
+
/* @__PURE__ */ jsx9("option", { value: "", children: "\u2014 Selecione \u2014" }),
|
|
1491
|
+
field.options.map((option) => /* @__PURE__ */ jsx9("option", { value: option, children: option }, option))
|
|
1492
|
+
] }),
|
|
1493
|
+
/* @__PURE__ */ jsx9(NativeSelect2.Indicator, {})
|
|
1494
|
+
] }));
|
|
1495
|
+
if (field.kind === "boolean") return wrap(/* @__PURE__ */ jsxs5(NativeSelect2.Root, { size: "sm", children: [
|
|
1496
|
+
/* @__PURE__ */ jsxs5(NativeSelect2.Field, { id, "aria-invalid": !!message, "aria-describedby": errorId, value: values[field.field] ?? "", ...controlProps(!!message), onChange: (event) => change(field.field, event.currentTarget.value), children: [
|
|
1497
|
+
/* @__PURE__ */ jsx9("option", { value: "", children: "\u2014 Selecione \u2014" }),
|
|
1498
|
+
/* @__PURE__ */ jsx9("option", { value: "true", children: "Sim" }),
|
|
1499
|
+
/* @__PURE__ */ jsx9("option", { value: "false", children: "N\xE3o" })
|
|
1500
|
+
] }),
|
|
1501
|
+
/* @__PURE__ */ jsx9(NativeSelect2.Indicator, {})
|
|
1502
|
+
] }));
|
|
1503
|
+
if (multiline) return wrap(/* @__PURE__ */ jsx9(Textarea, { id, rows: field.rows ?? 4, resize: "vertical", "aria-invalid": !!message, "aria-describedby": errorId, value: values[field.field] ?? "", ...controlProps(!!message), lineHeight: "1.5", onChange: (event) => change(field.field, event.currentTarget.value) }));
|
|
1504
|
+
const type = field.kind === "date" ? "date" : ["number", "currency", "currencyConverted"].includes(field.kind ?? "") ? "number" : "text";
|
|
1505
|
+
return wrap(/* @__PURE__ */ jsx9(Input3, { id, type, step: type === "number" ? "any" : void 0, autoComplete: "off", "aria-invalid": !!message, "aria-describedby": errorId, value: values[field.field] ?? "", ...controlProps(!!message), onChange: (event) => change(field.field, event.currentTarget.value) }));
|
|
1506
|
+
}) })
|
|
1507
|
+
] }) }),
|
|
1508
|
+
/* @__PURE__ */ jsxs5(Flex4, { minH: "68px", px: "20px", py: "14px", justify: "flex-end", gap: "9px", borderTopWidth: "1px", borderColor: "#E5E9ED", children: [
|
|
1509
|
+
/* @__PURE__ */ jsx9(Button4, { type: "button", minH: "35px", px: "13px", color: "#475467", bg: "white", borderWidth: "1px", borderColor: "#D5DDE3", borderRadius: "6px", fontSize: "11px", fontWeight: "600", disabled: submitting, onClick: onCancel, children: "Cancelar" }),
|
|
1510
|
+
/* @__PURE__ */ jsxs5(Button4, { type: "submit", minH: "35px", px: "13px", colorPalette: "brand", borderRadius: "6px", fontSize: "11px", fontWeight: "600", loading: submitting, children: [
|
|
1511
|
+
/* @__PURE__ */ jsx9(CheckIcon, {}),
|
|
1512
|
+
" Salvar"
|
|
1513
|
+
] })
|
|
647
1514
|
] })
|
|
648
|
-
] })
|
|
649
|
-
] });
|
|
1515
|
+
] })
|
|
1516
|
+
] }) });
|
|
650
1517
|
}
|
|
651
1518
|
|
|
652
1519
|
// src/components/CoreCollection.tsx
|
|
653
|
-
import { jsx as
|
|
654
|
-
function CoreCollection({ model, label, mode = "dynamic", endpoint, columns, form, importExport }) {
|
|
1520
|
+
import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1521
|
+
function CoreCollection({ model, label, section = "Cole\xE7\xE3o", mode = "dynamic", endpoint, columns, form, importExport }) {
|
|
655
1522
|
const needsSchema = mode !== "full" || !columns || !form || !endpoint;
|
|
656
1523
|
const schemaQuery = useModelSchema(needsSchema ? model : "");
|
|
657
1524
|
const schema = schemaQuery.data;
|
|
@@ -660,8 +1527,8 @@ function CoreCollection({ model, label, mode = "dynamic", endpoint, columns, for
|
|
|
660
1527
|
const resolvedForm = form ?? (schema ? formFieldsFromMeta(schema.fields) : []);
|
|
661
1528
|
const resource = useOonResource(basePath);
|
|
662
1529
|
const queryClient = useQueryClient();
|
|
663
|
-
const [formState, setFormState] =
|
|
664
|
-
const [formError, setFormError] =
|
|
1530
|
+
const [formState, setFormState] = useState6({ open: false });
|
|
1531
|
+
const [formError, setFormError] = useState6(null);
|
|
665
1532
|
const invalidate = () => queryClient.invalidateQueries({ queryKey: ["oon", "list", basePath] });
|
|
666
1533
|
const saveMutation = useMutation({
|
|
667
1534
|
mutationFn: (values) => {
|
|
@@ -674,54 +1541,68 @@ function CoreCollection({ model, label, mode = "dynamic", endpoint, columns, for
|
|
|
674
1541
|
setFormError(null);
|
|
675
1542
|
invalidate();
|
|
676
1543
|
},
|
|
677
|
-
onError: (
|
|
1544
|
+
onError: (error) => setFormError(error)
|
|
678
1545
|
});
|
|
679
1546
|
const deleteMutation = useMutation({
|
|
680
1547
|
mutationFn: (row) => resource.delete(row._id),
|
|
681
1548
|
onSuccess: invalidate
|
|
682
1549
|
});
|
|
683
|
-
if (needsSchema && schemaQuery.isLoading) return /* @__PURE__ */
|
|
684
|
-
if (needsSchema && schemaQuery.isError) return /* @__PURE__ */
|
|
1550
|
+
if (needsSchema && schemaQuery.isLoading) return /* @__PURE__ */ jsx10(LoadingScreen, {});
|
|
1551
|
+
if (needsSchema && schemaQuery.isError) return /* @__PURE__ */ jsx10(ErrorState, { error: schemaQuery.error });
|
|
685
1552
|
const title = label ?? schema?.name ?? model;
|
|
686
|
-
return /* @__PURE__ */
|
|
687
|
-
/* @__PURE__ */
|
|
688
|
-
|
|
1553
|
+
return /* @__PURE__ */ jsxs6(Stack3, { gap: 5, children: [
|
|
1554
|
+
/* @__PURE__ */ jsxs6(Box5, { children: [
|
|
1555
|
+
/* @__PURE__ */ jsx10(Text6, { fontSize: "11px", fontWeight: "700", color: "brand.500", textTransform: "uppercase", letterSpacing: "0.08em", mb: 1, children: section }),
|
|
1556
|
+
/* @__PURE__ */ jsx10(Heading3, { size: "lg", color: "#24323A", letterSpacing: "-0.02em", children: title }),
|
|
1557
|
+
/* @__PURE__ */ jsx10(Text6, { mt: 1, fontSize: "13px", color: "gray.500", children: "Consulte, filtre e mantenha os registros desta opera\xE7\xE3o." })
|
|
1558
|
+
] }),
|
|
1559
|
+
/* @__PURE__ */ jsx10(
|
|
1560
|
+
DataGrid,
|
|
1561
|
+
{
|
|
1562
|
+
resource,
|
|
1563
|
+
resourceKey: basePath,
|
|
1564
|
+
columns: resolvedColumns,
|
|
1565
|
+
onCreate: () => {
|
|
1566
|
+
setFormError(null);
|
|
1567
|
+
setFormState({ open: true });
|
|
1568
|
+
},
|
|
1569
|
+
onEdit: (row) => {
|
|
1570
|
+
setFormError(null);
|
|
1571
|
+
setFormState({ open: true, row });
|
|
1572
|
+
},
|
|
1573
|
+
onDelete: (row) => {
|
|
1574
|
+
if (window.confirm("Excluir este registro?")) deleteMutation.mutate(row);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
),
|
|
1578
|
+
formState.open ? /* @__PURE__ */ jsx10(
|
|
689
1579
|
DynamicForm,
|
|
690
1580
|
{
|
|
691
1581
|
title: formState.row ? `Editar ${title}` : `Novo ${title}`,
|
|
1582
|
+
eyebrow: section,
|
|
692
1583
|
fields: resolvedForm,
|
|
693
1584
|
initialValues: formState.row,
|
|
694
1585
|
submitting: saveMutation.isPending,
|
|
695
1586
|
error: formError,
|
|
696
|
-
onSubmit: (values) =>
|
|
1587
|
+
onSubmit: (values) => {
|
|
1588
|
+
setFormError(null);
|
|
1589
|
+
saveMutation.mutate(values);
|
|
1590
|
+
},
|
|
1591
|
+
onFieldChange: () => setFormError(null),
|
|
697
1592
|
onCancel: () => {
|
|
698
1593
|
setFormState({ open: false });
|
|
699
1594
|
setFormError(null);
|
|
700
1595
|
}
|
|
701
1596
|
}
|
|
702
|
-
) :
|
|
703
|
-
|
|
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
|
-
] })
|
|
1597
|
+
) : null,
|
|
1598
|
+
importExport ? null : null
|
|
718
1599
|
] });
|
|
719
1600
|
}
|
|
720
1601
|
|
|
721
1602
|
// src/components/CoreDocument.tsx
|
|
722
|
-
import { jsx as
|
|
1603
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
723
1604
|
function CoreDocument({ model, label, endpoint, columns, form }) {
|
|
724
|
-
return /* @__PURE__ */
|
|
1605
|
+
return /* @__PURE__ */ jsx11(
|
|
725
1606
|
CoreCollection,
|
|
726
1607
|
{
|
|
727
1608
|
model,
|
|
@@ -735,25 +1616,48 @@ function CoreDocument({ model, label, endpoint, columns, form }) {
|
|
|
735
1616
|
}
|
|
736
1617
|
|
|
737
1618
|
// src/components/CorePipeline.tsx
|
|
738
|
-
import {
|
|
739
|
-
import {
|
|
740
|
-
import {
|
|
1619
|
+
import { useState as useState7 } from "react";
|
|
1620
|
+
import { Badge as Badge2, Box as Box6, Button as Button5, Flex as Flex5, Heading as Heading4, Stack as Stack4, Text as Text7 } from "@chakra-ui/react";
|
|
1621
|
+
import { useMutation as useMutation2, useQuery as useQuery4, useQueryClient as useQueryClient2 } from "@tanstack/react-query";
|
|
1622
|
+
import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1623
|
+
function ticketId(row) {
|
|
1624
|
+
const id = row._id ?? row.id;
|
|
1625
|
+
return id == null ? void 0 : String(id);
|
|
1626
|
+
}
|
|
741
1627
|
function CorePipeline({ model, label, endpoint, stageField = "status", stages, titleField }) {
|
|
742
1628
|
const schemaQuery = useModelSchema(model);
|
|
743
1629
|
const schema = schemaQuery.data;
|
|
744
1630
|
const basePath = endpoint ?? schema?.basePath ?? `/${model.toLowerCase()}s`;
|
|
745
1631
|
const resource = useOonResource(basePath);
|
|
746
|
-
const
|
|
1632
|
+
const queryClient = useQueryClient2();
|
|
1633
|
+
const [selectedTicket, setSelectedTicket] = useState7(null);
|
|
1634
|
+
const [formError, setFormError] = useState7(null);
|
|
1635
|
+
const listQuery = useQuery4({
|
|
747
1636
|
queryKey: ["oon", "pipeline", basePath],
|
|
748
1637
|
queryFn: () => resource.list({ pageSize: 200 }),
|
|
749
1638
|
enabled: !!schema
|
|
750
1639
|
});
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
1640
|
+
const saveMutation = useMutation2({
|
|
1641
|
+
mutationFn: (values) => {
|
|
1642
|
+
const id = selectedTicket ? ticketId(selectedTicket) : void 0;
|
|
1643
|
+
if (!id) throw new Error("N\xE3o foi poss\xEDvel identificar o ticket selecionado.");
|
|
1644
|
+
return resource.update(id, values);
|
|
1645
|
+
},
|
|
1646
|
+
onSuccess: async () => {
|
|
1647
|
+
await queryClient.invalidateQueries({ queryKey: ["oon", "pipeline", basePath] });
|
|
1648
|
+
setSelectedTicket(null);
|
|
1649
|
+
setFormError(null);
|
|
1650
|
+
},
|
|
1651
|
+
onError: (error) => setFormError(error)
|
|
1652
|
+
});
|
|
1653
|
+
if (schemaQuery.isLoading || listQuery.isLoading) return /* @__PURE__ */ jsx12(LoadingScreen, {});
|
|
1654
|
+
if (schemaQuery.isError) return /* @__PURE__ */ jsx12(ErrorState, { error: schemaQuery.error });
|
|
1655
|
+
if (listQuery.isError) return /* @__PURE__ */ jsx12(ErrorState, { error: listQuery.error });
|
|
1656
|
+
const stageFieldMeta = schema?.fields.find((field) => field.name === stageField);
|
|
1657
|
+
const resolvedStages = stages ?? (stageFieldMeta?.options ?? []).map((option) => ({ id: option, label: option }));
|
|
1658
|
+
const title = titleField ?? schema?.fields.find((field) => field.kind === "string")?.name ?? schema?.searchable[0] ?? "_id";
|
|
1659
|
+
const detailFields = (schema?.fields ?? []).filter((field) => field.name !== stageField && field.name !== title && !["raw"].includes(field.kind)).slice(0, 3);
|
|
1660
|
+
const formFields = formFieldsFromMeta(schema?.fields ?? []);
|
|
757
1661
|
const rows = listQuery.data?.results ?? [];
|
|
758
1662
|
const byStage = /* @__PURE__ */ new Map();
|
|
759
1663
|
for (const stage of resolvedStages) byStage.set(stage.id, []);
|
|
@@ -762,25 +1666,101 @@ function CorePipeline({ model, label, endpoint, stageField = "status", stages, t
|
|
|
762
1666
|
if (!byStage.has(key)) byStage.set(key, []);
|
|
763
1667
|
byStage.get(key).push(row);
|
|
764
1668
|
}
|
|
765
|
-
return /* @__PURE__ */
|
|
766
|
-
/* @__PURE__ */
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
1669
|
+
return /* @__PURE__ */ jsxs7(Stack4, { gap: 5, children: [
|
|
1670
|
+
/* @__PURE__ */ jsxs7(Box6, { children: [
|
|
1671
|
+
/* @__PURE__ */ jsx12(Text7, { fontSize: "11px", fontWeight: "700", color: "brand.500", textTransform: "uppercase", letterSpacing: "0.08em", mb: 1, children: "Esteira de processo" }),
|
|
1672
|
+
/* @__PURE__ */ jsx12(Heading4, { size: "lg", color: "#24323A", letterSpacing: "-0.02em", children: label ?? schema?.name ?? model }),
|
|
1673
|
+
/* @__PURE__ */ jsx12(Text7, { mt: 1, fontSize: "13px", color: "gray.500", children: "Acompanhe os tickets por etapa e visualize os principais dados de cada item." })
|
|
1674
|
+
] }),
|
|
1675
|
+
/* @__PURE__ */ jsx12(Flex5, { gap: 4, align: "stretch", overflowX: "auto", pb: 3, minH: "420px", children: Array.from(byStage.entries()).map(([stageId, items]) => {
|
|
1676
|
+
const stageLabel = resolvedStages.find((stage) => stage.id === stageId)?.label ?? (stageId || "Sem etapa");
|
|
1677
|
+
return /* @__PURE__ */ jsxs7(
|
|
1678
|
+
Box6,
|
|
1679
|
+
{
|
|
1680
|
+
minW: "300px",
|
|
1681
|
+
maxW: "340px",
|
|
1682
|
+
flex: "1 0 300px",
|
|
1683
|
+
bg: "#E8ECEF",
|
|
1684
|
+
borderRadius: "12px",
|
|
1685
|
+
p: 3,
|
|
1686
|
+
borderWidth: "1px",
|
|
1687
|
+
borderColor: "#DDE3E7",
|
|
1688
|
+
children: [
|
|
1689
|
+
/* @__PURE__ */ jsxs7(Flex5, { align: "center", justify: "space-between", mb: 3, px: 1, children: [
|
|
1690
|
+
/* @__PURE__ */ jsx12(Text7, { fontWeight: "700", fontSize: "12px", color: "#33434C", textTransform: "uppercase", letterSpacing: "0.03em", children: stageLabel }),
|
|
1691
|
+
/* @__PURE__ */ jsx12(Badge2, { colorPalette: "brand", variant: "solid", borderRadius: "full", minW: "24px", justifyContent: "center", children: items.length })
|
|
1692
|
+
] }),
|
|
1693
|
+
/* @__PURE__ */ jsx12(Stack4, { gap: 2, children: items.length === 0 ? /* @__PURE__ */ jsx12(Box6, { borderWidth: "1px", borderStyle: "dashed", borderColor: "#C8D0D5", borderRadius: "10px", p: 5, textAlign: "center", children: /* @__PURE__ */ jsx12(Text7, { fontSize: "12px", color: "gray.500", children: "Nenhum ticket nesta etapa." }) }) : items.map((row, index) => {
|
|
1694
|
+
const ticketTitle = formatCell(row[title], "string");
|
|
1695
|
+
return /* @__PURE__ */ jsxs7(
|
|
1696
|
+
Button5,
|
|
1697
|
+
{
|
|
1698
|
+
type: "button",
|
|
1699
|
+
variant: "plain",
|
|
1700
|
+
display: "block",
|
|
1701
|
+
w: "100%",
|
|
1702
|
+
h: "auto",
|
|
1703
|
+
minH: "auto",
|
|
1704
|
+
p: 3,
|
|
1705
|
+
bg: "white",
|
|
1706
|
+
borderRadius: "10px",
|
|
1707
|
+
borderWidth: "1px",
|
|
1708
|
+
borderColor: "#DDE3E7",
|
|
1709
|
+
boxShadow: "0 3px 10px rgba(7, 38, 46, 0.05)",
|
|
1710
|
+
textAlign: "left",
|
|
1711
|
+
whiteSpace: "normal",
|
|
1712
|
+
transition: "all 0.15s ease",
|
|
1713
|
+
"aria-label": `Abrir ticket ${ticketTitle}`,
|
|
1714
|
+
onClick: () => {
|
|
1715
|
+
setFormError(null);
|
|
1716
|
+
setSelectedTicket(row);
|
|
1717
|
+
},
|
|
1718
|
+
_hover: { borderColor: "brand.300", transform: "translateY(-1px)", boxShadow: "0 7px 18px rgba(7, 38, 46, 0.09)" },
|
|
1719
|
+
_focusVisible: { borderColor: "brand.500", boxShadow: "0 0 0 3px rgba(4, 116, 175, 0.16)" },
|
|
1720
|
+
children: [
|
|
1721
|
+
/* @__PURE__ */ jsx12(Text7, { fontSize: "13px", fontWeight: "700", color: "#24323A", mb: 2, lineClamp: 2, children: ticketTitle }),
|
|
1722
|
+
/* @__PURE__ */ jsx12(Stack4, { gap: 1, children: detailFields.map((field) => /* @__PURE__ */ jsxs7(Flex5, { gap: 2, align: "baseline", justify: "space-between", children: [
|
|
1723
|
+
/* @__PURE__ */ jsx12(Text7, { fontSize: "10px", color: "gray.500", textTransform: "uppercase", letterSpacing: "0.03em", children: fieldLabel(field) }),
|
|
1724
|
+
/* @__PURE__ */ jsx12(Text7, { fontSize: "11px", color: "#46545C", fontWeight: "500", textAlign: "right", lineClamp: 1, children: formatCell(row[field.name], field.kind) })
|
|
1725
|
+
] }, field.name)) })
|
|
1726
|
+
]
|
|
1727
|
+
},
|
|
1728
|
+
ticketId(row) ?? index
|
|
1729
|
+
);
|
|
1730
|
+
}) })
|
|
1731
|
+
]
|
|
1732
|
+
},
|
|
1733
|
+
stageId
|
|
1734
|
+
);
|
|
1735
|
+
}) }),
|
|
1736
|
+
selectedTicket ? /* @__PURE__ */ jsx12(
|
|
1737
|
+
DynamicForm,
|
|
1738
|
+
{
|
|
1739
|
+
title: `Editar ${schema?.singular ?? label ?? model}`,
|
|
1740
|
+
eyebrow: "Ticket",
|
|
1741
|
+
fields: formFields,
|
|
1742
|
+
initialValues: selectedTicket,
|
|
1743
|
+
submitting: saveMutation.isPending,
|
|
1744
|
+
error: formError,
|
|
1745
|
+
onSubmit: (values) => {
|
|
1746
|
+
setFormError(null);
|
|
1747
|
+
saveMutation.mutate(values);
|
|
1748
|
+
},
|
|
1749
|
+
onFieldChange: () => setFormError(null),
|
|
1750
|
+
onCancel: () => {
|
|
1751
|
+
if (saveMutation.isPending) return;
|
|
1752
|
+
setSelectedTicket(null);
|
|
1753
|
+
setFormError(null);
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
) : null
|
|
777
1757
|
] });
|
|
778
1758
|
}
|
|
779
1759
|
|
|
780
1760
|
// src/components/CoreDashboard.tsx
|
|
781
|
-
import { Box as
|
|
1761
|
+
import { Box as Box7, Heading as Heading5, SimpleGrid as SimpleGrid2, Stack as Stack5, Text as Text8 } from "@chakra-ui/react";
|
|
782
1762
|
import { useQueries } from "@tanstack/react-query";
|
|
783
|
-
import { jsx as
|
|
1763
|
+
import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
784
1764
|
function CoreDashboard({ label = "Dashboard", widgets = [] }) {
|
|
785
1765
|
const { resource } = useOonApi();
|
|
786
1766
|
const queries = useQueries({
|
|
@@ -797,17 +1777,17 @@ function CoreDashboard({ label = "Dashboard", widgets = [] }) {
|
|
|
797
1777
|
}))
|
|
798
1778
|
});
|
|
799
1779
|
let queryIdx = 0;
|
|
800
|
-
return /* @__PURE__ */
|
|
801
|
-
/* @__PURE__ */
|
|
802
|
-
/* @__PURE__ */
|
|
1780
|
+
return /* @__PURE__ */ jsxs8(Stack5, { gap: 6, children: [
|
|
1781
|
+
/* @__PURE__ */ jsx13(Heading5, { size: "lg", children: label }),
|
|
1782
|
+
/* @__PURE__ */ jsx13(SimpleGrid2, { columns: { base: 1, md: 2, lg: 3 }, gap: 4, children: widgets.map((w) => {
|
|
803
1783
|
if (w.kind === "custom" && w.render) {
|
|
804
|
-
return /* @__PURE__ */
|
|
1784
|
+
return /* @__PURE__ */ jsx13(Box7, { borderWidth: "1px", borderColor: "gray.200", borderRadius: "md", bg: "white", p: 5, children: w.render() }, w.id);
|
|
805
1785
|
}
|
|
806
1786
|
const q = queries[queryIdx++];
|
|
807
1787
|
const value = q?.isLoading ? "\u2026" : q?.isError ? "\u2014" : formatWidget(q?.data, w.kind, w.field);
|
|
808
|
-
return /* @__PURE__ */
|
|
809
|
-
/* @__PURE__ */
|
|
810
|
-
/* @__PURE__ */
|
|
1788
|
+
return /* @__PURE__ */ jsxs8(Box7, { borderWidth: "1px", borderColor: "gray.200", borderRadius: "md", bg: "white", p: 5, children: [
|
|
1789
|
+
/* @__PURE__ */ jsx13(Text8, { fontSize: "sm", color: "gray.500", children: w.label }),
|
|
1790
|
+
/* @__PURE__ */ jsx13(Heading5, { size: "xl", mt: 1, children: value })
|
|
811
1791
|
] }, w.id);
|
|
812
1792
|
}) })
|
|
813
1793
|
] });
|
|
@@ -821,7 +1801,7 @@ function formatWidget(value, kind, field) {
|
|
|
821
1801
|
}
|
|
822
1802
|
|
|
823
1803
|
// src/app/moduleRegistry.tsx
|
|
824
|
-
import { jsx as
|
|
1804
|
+
import { jsx as jsx14 } from "react/jsx-runtime";
|
|
825
1805
|
function slug(value) {
|
|
826
1806
|
return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
827
1807
|
}
|
|
@@ -844,7 +1824,7 @@ function renderView(view) {
|
|
|
844
1824
|
switch (view.type) {
|
|
845
1825
|
case "collection": {
|
|
846
1826
|
const v = view;
|
|
847
|
-
return /* @__PURE__ */
|
|
1827
|
+
return /* @__PURE__ */ jsx14(
|
|
848
1828
|
CoreCollection,
|
|
849
1829
|
{
|
|
850
1830
|
model: v.model,
|
|
@@ -858,11 +1838,11 @@ function renderView(view) {
|
|
|
858
1838
|
}
|
|
859
1839
|
case "document": {
|
|
860
1840
|
const v = view;
|
|
861
|
-
return /* @__PURE__ */
|
|
1841
|
+
return /* @__PURE__ */ jsx14(CoreDocument, { model: v.model, label: viewLabel(v), approval: v.approval, attachments: v.attachments });
|
|
862
1842
|
}
|
|
863
1843
|
case "pipeline": {
|
|
864
1844
|
const v = view;
|
|
865
|
-
return /* @__PURE__ */
|
|
1845
|
+
return /* @__PURE__ */ jsx14(
|
|
866
1846
|
CorePipeline,
|
|
867
1847
|
{
|
|
868
1848
|
model: v.model,
|
|
@@ -874,7 +1854,7 @@ function renderView(view) {
|
|
|
874
1854
|
}
|
|
875
1855
|
case "dashboard": {
|
|
876
1856
|
const v = view;
|
|
877
|
-
return /* @__PURE__ */
|
|
1857
|
+
return /* @__PURE__ */ jsx14(CoreDashboard, { label: viewLabel(v), widgets: v.widgets });
|
|
878
1858
|
}
|
|
879
1859
|
default:
|
|
880
1860
|
return null;
|
|
@@ -922,20 +1902,23 @@ function resolveRegistry(config) {
|
|
|
922
1902
|
}
|
|
923
1903
|
|
|
924
1904
|
// src/app/createOonRouter.tsx
|
|
925
|
-
import {
|
|
1905
|
+
import {
|
|
1906
|
+
Navigate as Navigate4,
|
|
1907
|
+
createBrowserRouter
|
|
1908
|
+
} from "react-router-dom";
|
|
926
1909
|
|
|
927
1910
|
// src/shell/AuthenticatedLayout.tsx
|
|
928
|
-
import { Box as
|
|
1911
|
+
import { Box as Box9, Button as Button6, Flex as Flex7, Spacer as Spacer2, Text as Text10 } from "@chakra-ui/react";
|
|
929
1912
|
import { Outlet } from "react-router-dom";
|
|
930
1913
|
|
|
931
1914
|
// src/shell/Sidebar.tsx
|
|
932
|
-
import { Box as
|
|
1915
|
+
import { Box as Box8, Flex as Flex6, Image, Stack as Stack6, Text as Text9 } from "@chakra-ui/react";
|
|
933
1916
|
import { NavLink } from "react-router-dom";
|
|
934
|
-
import { jsx as
|
|
1917
|
+
import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
935
1918
|
function groupBySection(items) {
|
|
936
1919
|
const groups = /* @__PURE__ */ new Map();
|
|
937
1920
|
for (const item of items) {
|
|
938
|
-
const section = item.section ?? "";
|
|
1921
|
+
const section = item.section ?? "Geral";
|
|
939
1922
|
if (!groups.has(section)) groups.set(section, []);
|
|
940
1923
|
groups.get(section).push(item);
|
|
941
1924
|
}
|
|
@@ -944,85 +1927,762 @@ function groupBySection(items) {
|
|
|
944
1927
|
function Sidebar({ app, menu }) {
|
|
945
1928
|
const { hasPermission } = useOonAuth();
|
|
946
1929
|
const visible = menu.filter((item) => hasPermission(item.permissions));
|
|
947
|
-
return /* @__PURE__ */
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
1930
|
+
return /* @__PURE__ */ jsxs9(
|
|
1931
|
+
Box8,
|
|
1932
|
+
{
|
|
1933
|
+
as: "nav",
|
|
1934
|
+
w: { base: "76px", lg: "248px" },
|
|
1935
|
+
minH: "100vh",
|
|
1936
|
+
borderRightWidth: "1px",
|
|
1937
|
+
borderColor: "#E8ECEF",
|
|
1938
|
+
px: { base: 2, lg: 3 },
|
|
1939
|
+
py: 4,
|
|
1940
|
+
bg: "white",
|
|
1941
|
+
position: "sticky",
|
|
1942
|
+
top: 0,
|
|
1943
|
+
alignSelf: "flex-start",
|
|
1944
|
+
overflowY: "auto",
|
|
1945
|
+
children: [
|
|
1946
|
+
/* @__PURE__ */ jsxs9(Flex6, { align: "center", gap: 3, mb: 6, px: { base: 1, lg: 2 }, minH: "42px", children: [
|
|
1947
|
+
app.logo ? /* @__PURE__ */ jsx15(Image, { src: app.logo, alt: app.name, maxH: "36px", maxW: { base: "44px", lg: "150px" }, objectFit: "contain" }) : /* @__PURE__ */ jsx15(
|
|
1948
|
+
Flex6,
|
|
1949
|
+
{
|
|
1950
|
+
w: "38px",
|
|
1951
|
+
h: "38px",
|
|
1952
|
+
flex: "0 0 auto",
|
|
1953
|
+
borderRadius: "12px",
|
|
1954
|
+
align: "center",
|
|
1955
|
+
justify: "center",
|
|
1956
|
+
bg: "brand.500",
|
|
1957
|
+
color: "white",
|
|
1958
|
+
fontWeight: "700",
|
|
1959
|
+
fontSize: "lg",
|
|
1960
|
+
children: "O"
|
|
1961
|
+
}
|
|
1962
|
+
),
|
|
1963
|
+
/* @__PURE__ */ jsxs9(Box8, { display: { base: "none", lg: "block" }, minW: 0, children: [
|
|
1964
|
+
/* @__PURE__ */ jsx15(Text9, { fontWeight: "700", fontSize: "sm", color: "#07262E", lineClamp: 1, children: app.name }),
|
|
1965
|
+
/* @__PURE__ */ jsx15(Text9, { fontSize: "10px", color: "gray.500", textTransform: "uppercase", letterSpacing: "0.08em", children: "Opera\xE7\xE3o digital" })
|
|
1966
|
+
] })
|
|
1967
|
+
] }),
|
|
1968
|
+
/* @__PURE__ */ jsx15(Stack6, { gap: 5, children: groupBySection(visible).map(([section, items]) => /* @__PURE__ */ jsxs9(Stack6, { gap: 1, children: [
|
|
1969
|
+
/* @__PURE__ */ jsx15(
|
|
1970
|
+
Text9,
|
|
1971
|
+
{
|
|
1972
|
+
display: { base: "none", lg: "block" },
|
|
1973
|
+
fontSize: "10px",
|
|
1974
|
+
fontWeight: "600",
|
|
1975
|
+
color: "gray.400",
|
|
1976
|
+
textTransform: "uppercase",
|
|
1977
|
+
letterSpacing: "0.08em",
|
|
1978
|
+
px: 3,
|
|
1979
|
+
mb: 1,
|
|
1980
|
+
children: section
|
|
1981
|
+
}
|
|
1982
|
+
),
|
|
1983
|
+
items.map((item) => /* @__PURE__ */ jsx15(NavLink, { to: item.href, end: item.href === "/", title: item.label, children: ({ isActive }) => /* @__PURE__ */ jsxs9(
|
|
1984
|
+
Flex6,
|
|
1985
|
+
{
|
|
1986
|
+
align: "center",
|
|
1987
|
+
justify: { base: "center", lg: "flex-start" },
|
|
1988
|
+
gap: 3,
|
|
1989
|
+
px: { base: 2, lg: 3 },
|
|
1990
|
+
py: "10px",
|
|
1991
|
+
borderRadius: "9px",
|
|
1992
|
+
fontSize: "13px",
|
|
1993
|
+
fontWeight: isActive ? "600" : "500",
|
|
1994
|
+
bg: isActive ? "brand.50" : "transparent",
|
|
1995
|
+
color: isActive ? "brand.700" : "#46545C",
|
|
1996
|
+
borderLeftWidth: "3px",
|
|
1997
|
+
borderLeftColor: isActive ? "brand.500" : "transparent",
|
|
1998
|
+
transition: "all 0.15s ease",
|
|
1999
|
+
_hover: { bg: isActive ? "brand.50" : "#F8F9FA", color: "brand.700" },
|
|
2000
|
+
children: [
|
|
2001
|
+
item.icon ? /* @__PURE__ */ jsx15(Box8, { flex: "0 0 auto", children: item.icon }) : /* @__PURE__ */ jsx15(Box8, { w: "7px", h: "7px", borderRadius: "full", bg: isActive ? "brand.500" : "gray.300" }),
|
|
2002
|
+
/* @__PURE__ */ jsx15(Text9, { display: { base: "none", lg: "block" }, lineClamp: 1, children: item.label })
|
|
2003
|
+
]
|
|
2004
|
+
}
|
|
2005
|
+
) }, item.href))
|
|
2006
|
+
] }, section)) })
|
|
2007
|
+
]
|
|
2008
|
+
}
|
|
2009
|
+
);
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
// src/shell/AuthenticatedLayout.tsx
|
|
2013
|
+
import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2014
|
+
function AuthenticatedLayout({ app, menu }) {
|
|
2015
|
+
const { user, logout } = useOonAuth();
|
|
2016
|
+
return /* @__PURE__ */ jsxs10(Flex7, { align: "stretch", minH: "100vh", bg: "#F8F9FA", children: [
|
|
2017
|
+
/* @__PURE__ */ jsx16(Sidebar, { app, menu }),
|
|
2018
|
+
/* @__PURE__ */ jsxs10(Flex7, { direction: "column", flex: "1", minW: 0, children: [
|
|
2019
|
+
/* @__PURE__ */ jsxs10(
|
|
2020
|
+
Flex7,
|
|
956
2021
|
{
|
|
2022
|
+
as: "header",
|
|
957
2023
|
align: "center",
|
|
958
|
-
|
|
959
|
-
px:
|
|
960
|
-
py:
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
2024
|
+
minH: "64px",
|
|
2025
|
+
px: { base: 4, lg: 6 },
|
|
2026
|
+
py: 3,
|
|
2027
|
+
borderBottomWidth: "1px",
|
|
2028
|
+
borderColor: "#E8ECEF",
|
|
2029
|
+
bg: "rgba(255, 255, 255, 0.96)",
|
|
2030
|
+
backdropFilter: "blur(10px)",
|
|
2031
|
+
position: "sticky",
|
|
2032
|
+
top: 0,
|
|
2033
|
+
zIndex: 10,
|
|
967
2034
|
children: [
|
|
968
|
-
|
|
969
|
-
|
|
2035
|
+
/* @__PURE__ */ jsxs10(Box9, { children: [
|
|
2036
|
+
/* @__PURE__ */ jsx16(Text10, { fontSize: "xs", fontWeight: "600", color: "brand.500", textTransform: "uppercase", letterSpacing: "0.08em", children: "Central Oon" }),
|
|
2037
|
+
/* @__PURE__ */ jsx16(Text10, { fontSize: "sm", fontWeight: "600", color: "#24323A", children: app.title ?? app.name })
|
|
2038
|
+
] }),
|
|
2039
|
+
/* @__PURE__ */ jsx16(Spacer2, {}),
|
|
2040
|
+
user ? /* @__PURE__ */ jsxs10(Flex7, { align: "center", gap: 3, children: [
|
|
2041
|
+
/* @__PURE__ */ jsxs10(Box9, { textAlign: "right", display: { base: "none", md: "block" }, children: [
|
|
2042
|
+
/* @__PURE__ */ jsx16(Text10, { fontSize: "sm", fontWeight: "600", color: "#24323A", children: user.nome }),
|
|
2043
|
+
user.email ? /* @__PURE__ */ jsx16(Text10, { fontSize: "xs", color: "gray.500", children: user.email }) : null
|
|
2044
|
+
] }),
|
|
2045
|
+
/* @__PURE__ */ jsx16(Button6, { size: "sm", variant: "outline", colorPalette: "brand", borderRadius: "8px", onClick: logout, children: "Sair" })
|
|
2046
|
+
] }) : null
|
|
970
2047
|
]
|
|
971
2048
|
}
|
|
972
|
-
)
|
|
973
|
-
|
|
2049
|
+
),
|
|
2050
|
+
/* @__PURE__ */ jsx16(Box9, { as: "main", flex: "1", p: { base: 4, lg: 6 }, minW: 0, children: /* @__PURE__ */ jsx16(Box9, { maxW: "1680px", mx: "auto", children: /* @__PURE__ */ jsx16(Outlet, {}) }) })
|
|
2051
|
+
] })
|
|
974
2052
|
] });
|
|
975
2053
|
}
|
|
976
2054
|
|
|
977
|
-
// src/
|
|
978
|
-
import {
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
2055
|
+
// src/security/AccessDeniedPage.tsx
|
|
2056
|
+
import { Box as Box10, Button as Button7, Flex as Flex8, Heading as Heading6, Text as Text11, VStack } from "@chakra-ui/react";
|
|
2057
|
+
import { useNavigate } from "react-router-dom";
|
|
2058
|
+
import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2059
|
+
function AccessDeniedPage({ app }) {
|
|
2060
|
+
const { logout } = useOonAuth();
|
|
2061
|
+
const navigate = useNavigate();
|
|
2062
|
+
function useAnotherAccount() {
|
|
2063
|
+
logout();
|
|
2064
|
+
navigate("/login", { replace: true });
|
|
2065
|
+
}
|
|
2066
|
+
return /* @__PURE__ */ jsx17(Flex8, { minH: "100vh", align: "center", justify: "center", bg: "#F5F8FA", px: 4, children: /* @__PURE__ */ jsx17(
|
|
2067
|
+
Box10,
|
|
2068
|
+
{
|
|
2069
|
+
width: "100%",
|
|
2070
|
+
maxW: "560px",
|
|
2071
|
+
bg: "white",
|
|
2072
|
+
borderWidth: "1px",
|
|
2073
|
+
borderColor: "#E4EBEF",
|
|
2074
|
+
borderRadius: "18px",
|
|
2075
|
+
boxShadow: "0 24px 70px rgba(7, 38, 46, 0.10)",
|
|
2076
|
+
p: { base: 6, md: 10 },
|
|
2077
|
+
children: /* @__PURE__ */ jsxs11(VStack, { align: "stretch", gap: 6, children: [
|
|
2078
|
+
/* @__PURE__ */ jsx17(
|
|
2079
|
+
Box10,
|
|
2080
|
+
{
|
|
2081
|
+
width: "56px",
|
|
2082
|
+
height: "56px",
|
|
2083
|
+
display: "flex",
|
|
2084
|
+
alignItems: "center",
|
|
2085
|
+
justifyContent: "center",
|
|
2086
|
+
borderRadius: "16px",
|
|
2087
|
+
bg: "orange.50",
|
|
2088
|
+
color: "orange.700",
|
|
2089
|
+
fontSize: "2xl",
|
|
2090
|
+
fontWeight: "800",
|
|
2091
|
+
"aria-hidden": "true",
|
|
2092
|
+
children: "!"
|
|
2093
|
+
}
|
|
2094
|
+
),
|
|
2095
|
+
/* @__PURE__ */ jsxs11(Box10, { children: [
|
|
2096
|
+
/* @__PURE__ */ jsx17(
|
|
2097
|
+
Text11,
|
|
2098
|
+
{
|
|
2099
|
+
color: "#39BFED",
|
|
2100
|
+
fontSize: "xs",
|
|
2101
|
+
fontWeight: "700",
|
|
2102
|
+
letterSpacing: "0.12em",
|
|
2103
|
+
textTransform: "uppercase",
|
|
2104
|
+
children: "Acesso negado"
|
|
2105
|
+
}
|
|
2106
|
+
),
|
|
2107
|
+
/* @__PURE__ */ jsx17(Heading6, { mt: 2, size: "lg", color: "#07262E", children: "Voc\xEA n\xE3o possui permiss\xE3o para este aplicativo" }),
|
|
2108
|
+
/* @__PURE__ */ jsxs11(Text11, { mt: 3, color: "gray.600", children: [
|
|
2109
|
+
"Seu usu\xE1rio foi autenticado, mas n\xE3o existe uma permiss\xE3o ativa para acessar ",
|
|
2110
|
+
app.title ?? app.name,
|
|
2111
|
+
". Solicite o acesso ao administrador da sua organiza\xE7\xE3o."
|
|
2112
|
+
] })
|
|
2113
|
+
] }),
|
|
2114
|
+
/* @__PURE__ */ jsx17(
|
|
2115
|
+
Button7,
|
|
2116
|
+
{
|
|
2117
|
+
alignSelf: "flex-start",
|
|
2118
|
+
bg: "#07262E",
|
|
2119
|
+
color: "white",
|
|
2120
|
+
_hover: { bg: "#0B3843" },
|
|
2121
|
+
onClick: useAnotherAccount,
|
|
2122
|
+
children: "Entrar com outra conta"
|
|
2123
|
+
}
|
|
2124
|
+
)
|
|
2125
|
+
] })
|
|
2126
|
+
}
|
|
2127
|
+
) });
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
// src/security/AppAccessGuard.tsx
|
|
2131
|
+
import { Navigate, useLocation } from "react-router-dom";
|
|
2132
|
+
import { Fragment, jsx as jsx18 } from "react/jsx-runtime";
|
|
2133
|
+
function loginRedirect(pathname, search) {
|
|
2134
|
+
const destination = `${pathname}${search}`;
|
|
2135
|
+
return `/login?redirect=${encodeURIComponent(destination)}`;
|
|
2136
|
+
}
|
|
2137
|
+
function AppAccessGuard({ children }) {
|
|
2138
|
+
const { status, retry } = useOonAuth();
|
|
2139
|
+
const location = useLocation();
|
|
2140
|
+
if (status === "loading") return /* @__PURE__ */ jsx18(LoadingScreen, {});
|
|
2141
|
+
if (status === "unauthenticated") {
|
|
2142
|
+
return /* @__PURE__ */ jsx18(
|
|
2143
|
+
Navigate,
|
|
2144
|
+
{
|
|
2145
|
+
to: loginRedirect(location.pathname, location.search),
|
|
2146
|
+
replace: true
|
|
2147
|
+
}
|
|
2148
|
+
);
|
|
2149
|
+
}
|
|
2150
|
+
if (status === "forbidden") {
|
|
2151
|
+
return /* @__PURE__ */ jsx18(Navigate, { to: "/acesso-negado", replace: true });
|
|
2152
|
+
}
|
|
2153
|
+
if (status === "error") {
|
|
2154
|
+
return /* @__PURE__ */ jsx18(
|
|
2155
|
+
LoadingScreen,
|
|
2156
|
+
{
|
|
2157
|
+
message: "N\xE3o foi poss\xEDvel validar seu acesso. Tente novamente.",
|
|
2158
|
+
actionLabel: "Tentar novamente",
|
|
2159
|
+
onAction: () => {
|
|
2160
|
+
void retry().catch(() => void 0);
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
);
|
|
2164
|
+
}
|
|
2165
|
+
return /* @__PURE__ */ jsx18(Fragment, { children });
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
// src/security/LoginPage.tsx
|
|
2169
|
+
import { useState as useState8 } from "react";
|
|
2170
|
+
import {
|
|
2171
|
+
Box as Box11,
|
|
2172
|
+
Button as Button8,
|
|
2173
|
+
Flex as Flex9,
|
|
2174
|
+
Heading as Heading7,
|
|
2175
|
+
Input as Input4,
|
|
2176
|
+
Text as Text12,
|
|
2177
|
+
VStack as VStack2
|
|
2178
|
+
} from "@chakra-ui/react";
|
|
2179
|
+
import { Navigate as Navigate2, useLocation as useLocation2 } from "react-router-dom";
|
|
2180
|
+
import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2181
|
+
function safeRedirect(search) {
|
|
2182
|
+
const value = new URLSearchParams(search).get("redirect") || "/";
|
|
2183
|
+
if (!value.startsWith("/") || value.startsWith("//")) return "/";
|
|
2184
|
+
if (value.startsWith("/login") || value.startsWith("/acesso-negado")) {
|
|
2185
|
+
return "/";
|
|
2186
|
+
}
|
|
2187
|
+
return value;
|
|
2188
|
+
}
|
|
2189
|
+
function LoginPage({ app }) {
|
|
2190
|
+
const { status, login } = useOonAuth();
|
|
2191
|
+
const location = useLocation2();
|
|
2192
|
+
const [email, setEmail] = useState8("");
|
|
2193
|
+
const [password, setPassword] = useState8("");
|
|
2194
|
+
const [submitting, setSubmitting] = useState8(false);
|
|
2195
|
+
const [message, setMessage] = useState8(null);
|
|
2196
|
+
const redirect = safeRedirect(location.search);
|
|
2197
|
+
if (status === "authenticated") {
|
|
2198
|
+
return /* @__PURE__ */ jsx19(Navigate2, { to: redirect, replace: true });
|
|
2199
|
+
}
|
|
2200
|
+
if (status === "forbidden") {
|
|
2201
|
+
return /* @__PURE__ */ jsx19(Navigate2, { to: "/acesso-negado", replace: true });
|
|
2202
|
+
}
|
|
2203
|
+
async function handleSubmit(event) {
|
|
2204
|
+
event.preventDefault();
|
|
2205
|
+
setSubmitting(true);
|
|
2206
|
+
setMessage(null);
|
|
2207
|
+
try {
|
|
2208
|
+
await login(email.trim(), password);
|
|
2209
|
+
} catch (error) {
|
|
2210
|
+
const authError = error;
|
|
2211
|
+
if (authError.status === 403) return;
|
|
2212
|
+
setMessage(
|
|
2213
|
+
authError.status === 401 ? "E-mail ou senha inv\xE1lidos." : authError.message || "N\xE3o foi poss\xEDvel entrar. Tente novamente."
|
|
2214
|
+
);
|
|
2215
|
+
} finally {
|
|
2216
|
+
setSubmitting(false);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
return /* @__PURE__ */ jsx19(Flex9, { minH: "100vh", align: "center", justify: "center", bg: "#F5F8FA", px: 4, children: /* @__PURE__ */ jsx19(
|
|
2220
|
+
Box11,
|
|
2221
|
+
{
|
|
2222
|
+
width: "100%",
|
|
2223
|
+
maxW: "440px",
|
|
2224
|
+
bg: "white",
|
|
2225
|
+
borderWidth: "1px",
|
|
2226
|
+
borderColor: "#E4EBEF",
|
|
2227
|
+
borderRadius: "18px",
|
|
2228
|
+
boxShadow: "0 24px 70px rgba(7, 38, 46, 0.10)",
|
|
2229
|
+
p: { base: 6, md: 9 },
|
|
2230
|
+
children: /* @__PURE__ */ jsx19("form", { onSubmit: handleSubmit, children: /* @__PURE__ */ jsxs12(VStack2, { align: "stretch", gap: 6, children: [
|
|
2231
|
+
/* @__PURE__ */ jsxs12(Box11, { children: [
|
|
2232
|
+
/* @__PURE__ */ jsx19(
|
|
2233
|
+
Text12,
|
|
2234
|
+
{
|
|
2235
|
+
color: "#39BFED",
|
|
2236
|
+
fontSize: "xs",
|
|
2237
|
+
fontWeight: "700",
|
|
2238
|
+
letterSpacing: "0.12em",
|
|
2239
|
+
textTransform: "uppercase",
|
|
2240
|
+
children: "Central Oon"
|
|
2241
|
+
}
|
|
2242
|
+
),
|
|
2243
|
+
/* @__PURE__ */ jsx19(Heading7, { mt: 2, size: "lg", color: "#07262E", children: app.title ?? app.name }),
|
|
2244
|
+
/* @__PURE__ */ jsx19(Text12, { mt: 2, color: "gray.600", fontSize: "sm", children: "Entre com seu usu\xE1rio autorizado para acessar este aplicativo." })
|
|
2245
|
+
] }),
|
|
2246
|
+
/* @__PURE__ */ jsxs12(VStack2, { align: "stretch", gap: 4, children: [
|
|
2247
|
+
/* @__PURE__ */ jsxs12(Box11, { children: [
|
|
2248
|
+
/* @__PURE__ */ jsx19(Text12, { mb: 2, fontSize: "sm", fontWeight: "600", color: "#24323A", children: "E-mail" }),
|
|
2249
|
+
/* @__PURE__ */ jsx19(
|
|
2250
|
+
Input4,
|
|
2251
|
+
{
|
|
2252
|
+
type: "email",
|
|
2253
|
+
value: email,
|
|
2254
|
+
onChange: (event) => setEmail(event.target.value),
|
|
2255
|
+
autoComplete: "email",
|
|
2256
|
+
placeholder: "voce@empresa.com.br",
|
|
2257
|
+
required: true
|
|
2258
|
+
}
|
|
2259
|
+
)
|
|
2260
|
+
] }),
|
|
2261
|
+
/* @__PURE__ */ jsxs12(Box11, { children: [
|
|
2262
|
+
/* @__PURE__ */ jsx19(Text12, { mb: 2, fontSize: "sm", fontWeight: "600", color: "#24323A", children: "Senha" }),
|
|
2263
|
+
/* @__PURE__ */ jsx19(
|
|
2264
|
+
Input4,
|
|
2265
|
+
{
|
|
2266
|
+
type: "password",
|
|
2267
|
+
value: password,
|
|
2268
|
+
onChange: (event) => setPassword(event.target.value),
|
|
2269
|
+
autoComplete: "current-password",
|
|
2270
|
+
placeholder: "Sua senha",
|
|
2271
|
+
required: true
|
|
2272
|
+
}
|
|
2273
|
+
)
|
|
2274
|
+
] })
|
|
2275
|
+
] }),
|
|
2276
|
+
message ? /* @__PURE__ */ jsx19(
|
|
2277
|
+
Box11,
|
|
2278
|
+
{
|
|
2279
|
+
role: "alert",
|
|
2280
|
+
borderRadius: "10px",
|
|
2281
|
+
bg: "red.50",
|
|
2282
|
+
color: "red.700",
|
|
2283
|
+
px: 4,
|
|
2284
|
+
py: 3,
|
|
2285
|
+
fontSize: "sm",
|
|
2286
|
+
children: message
|
|
2287
|
+
}
|
|
2288
|
+
) : null,
|
|
2289
|
+
/* @__PURE__ */ jsx19(
|
|
2290
|
+
Button8,
|
|
2291
|
+
{
|
|
2292
|
+
type: "submit",
|
|
2293
|
+
bg: "#07262E",
|
|
2294
|
+
color: "white",
|
|
2295
|
+
_hover: { bg: "#0B3843" },
|
|
2296
|
+
disabled: submitting || !email.trim() || !password,
|
|
2297
|
+
children: submitting ? "Validando acesso..." : "Entrar"
|
|
2298
|
+
}
|
|
2299
|
+
)
|
|
2300
|
+
] }) })
|
|
2301
|
+
}
|
|
2302
|
+
) });
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
// src/security/ActivationPage.tsx
|
|
2306
|
+
import { useEffect as useEffect5, useState as useState9 } from "react";
|
|
2307
|
+
import { Box as Box12, Button as Button9, Heading as Heading8, Input as Input5, Text as Text13, VStack as VStack3 } from "@chakra-ui/react";
|
|
2308
|
+
import { Fragment as Fragment2, jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2309
|
+
function ActivationPage() {
|
|
2310
|
+
const { http } = useOonApi();
|
|
2311
|
+
const activation = useActivation();
|
|
2312
|
+
const [step, setStep] = useState9(1);
|
|
2313
|
+
const [config, setConfig] = useState9(null);
|
|
2314
|
+
const [code, setCode] = useState9("");
|
|
2315
|
+
const [preview, setPreview] = useState9(null);
|
|
2316
|
+
const [global, setGlobal] = useState9({});
|
|
2317
|
+
const [app, setApp] = useState9({});
|
|
2318
|
+
const [rootEmail, setRootEmail] = useState9("");
|
|
2319
|
+
const [rootPassword, setRootPassword] = useState9("");
|
|
2320
|
+
const [error, setError] = useState9(null);
|
|
2321
|
+
const [submitting, setSubmitting] = useState9(false);
|
|
2322
|
+
useEffect5(() => {
|
|
2323
|
+
http.get("/ativacao/configuracao").then((response) => {
|
|
2324
|
+
setConfig(response.data);
|
|
2325
|
+
setGlobal(response.data.defaults || {});
|
|
2326
|
+
}).catch(
|
|
2327
|
+
() => setError("N\xE3o foi poss\xEDvel carregar a configura\xE7\xE3o de ativa\xE7\xE3o.")
|
|
2328
|
+
);
|
|
2329
|
+
}, [http]);
|
|
2330
|
+
useEffect5(() => {
|
|
2331
|
+
if (activation.status === "active") {
|
|
2332
|
+
window.location.replace("/login");
|
|
2333
|
+
}
|
|
2334
|
+
}, [activation.status]);
|
|
2335
|
+
async function activateRoot() {
|
|
2336
|
+
setSubmitting(true);
|
|
2337
|
+
setError(null);
|
|
2338
|
+
try {
|
|
2339
|
+
await http.post("/ativacao/ativar-root", {
|
|
2340
|
+
email: rootEmail.trim(),
|
|
2341
|
+
senha: rootPassword
|
|
2342
|
+
});
|
|
2343
|
+
setRootPassword("");
|
|
2344
|
+
setStep(6);
|
|
2345
|
+
await activation.refresh();
|
|
2346
|
+
window.setTimeout(() => window.location.replace("/login"), 900);
|
|
2347
|
+
} catch (caught) {
|
|
2348
|
+
setRootPassword("");
|
|
2349
|
+
setError(
|
|
2350
|
+
caught.message || "N\xE3o foi poss\xEDvel concluir a ativa\xE7\xE3o inicial."
|
|
2351
|
+
);
|
|
2352
|
+
} finally {
|
|
2353
|
+
setSubmitting(false);
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
async function validateCode() {
|
|
2357
|
+
setSubmitting(true);
|
|
2358
|
+
setError(null);
|
|
2359
|
+
try {
|
|
2360
|
+
const response = await http.post("/ativacao/validar-codigo", {
|
|
2361
|
+
codigoAtivacao: code
|
|
2362
|
+
});
|
|
2363
|
+
setPreview(response.data);
|
|
2364
|
+
setStep(2);
|
|
2365
|
+
} catch (caught) {
|
|
2366
|
+
setError(caught.message || "C\xF3digo inv\xE1lido.");
|
|
2367
|
+
} finally {
|
|
2368
|
+
setSubmitting(false);
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
async function completeMemberActivation() {
|
|
2372
|
+
setSubmitting(true);
|
|
2373
|
+
setError(null);
|
|
2374
|
+
try {
|
|
2375
|
+
await http.post("/ativacao/concluir", {
|
|
2376
|
+
codigoAtivacao: code,
|
|
2377
|
+
nomeInstancia: global.nomeInstancia,
|
|
2378
|
+
parametrosGlobais: global,
|
|
2379
|
+
parametrosAplicacao: app
|
|
2380
|
+
});
|
|
2381
|
+
setStep(6);
|
|
2382
|
+
setCode("");
|
|
2383
|
+
await activation.refresh();
|
|
2384
|
+
window.setTimeout(() => window.location.replace("/login"), 1200);
|
|
2385
|
+
} catch (caught) {
|
|
2386
|
+
setError(
|
|
2387
|
+
caught.message || "N\xE3o foi poss\xEDvel concluir a ativa\xE7\xE3o."
|
|
2388
|
+
);
|
|
2389
|
+
} finally {
|
|
2390
|
+
setSubmitting(false);
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
function renderField(field, values, setValues) {
|
|
2394
|
+
return /* @__PURE__ */ jsxs13(Box12, { w: "100%", children: [
|
|
2395
|
+
/* @__PURE__ */ jsxs13(Text13, { fontWeight: "medium", children: [
|
|
2396
|
+
field.label,
|
|
2397
|
+
field.required ? " *" : ""
|
|
991
2398
|
] }),
|
|
992
|
-
/* @__PURE__ */
|
|
993
|
-
|
|
2399
|
+
/* @__PURE__ */ jsx20(
|
|
2400
|
+
Input5,
|
|
2401
|
+
{
|
|
2402
|
+
type: field.type === "password" || field.type === "secret" ? "password" : field.type === "email" ? "email" : field.type === "url" ? "url" : field.type === "number" || field.type === "integer" ? "number" : "text",
|
|
2403
|
+
value: String(values[field.name] ?? field.defaultValue ?? ""),
|
|
2404
|
+
readOnly: field.readOnly,
|
|
2405
|
+
placeholder: field.placeholder,
|
|
2406
|
+
minLength: field.minLength,
|
|
2407
|
+
autoComplete: field.autoComplete,
|
|
2408
|
+
onChange: (event) => setValues({ ...values, [field.name]: event.target.value })
|
|
2409
|
+
}
|
|
2410
|
+
),
|
|
2411
|
+
field.helpText ? /* @__PURE__ */ jsx20(Text13, { fontSize: "sm", color: "gray.500", children: field.helpText }) : null
|
|
2412
|
+
] }, field.name);
|
|
2413
|
+
}
|
|
2414
|
+
const rootMode = config?.mode === "root-bootstrap" || activation.details?.role === "root";
|
|
2415
|
+
if (activation.status === "loading" || !config) {
|
|
2416
|
+
return /* @__PURE__ */ jsx20(Box12, { minH: "100vh", bg: "gray.50", p: 8, children: /* @__PURE__ */ jsxs13(
|
|
2417
|
+
VStack3,
|
|
2418
|
+
{
|
|
2419
|
+
maxW: "760px",
|
|
2420
|
+
mx: "auto",
|
|
2421
|
+
bg: "white",
|
|
2422
|
+
p: 8,
|
|
2423
|
+
borderRadius: "lg",
|
|
2424
|
+
align: "stretch",
|
|
2425
|
+
gap: 5,
|
|
2426
|
+
children: [
|
|
2427
|
+
/* @__PURE__ */ jsx20(Heading8, { children: "Preparando ativa\xE7\xE3o..." }),
|
|
2428
|
+
error ? /* @__PURE__ */ jsx20(Text13, { color: "red.600", children: error }) : null
|
|
2429
|
+
]
|
|
2430
|
+
}
|
|
2431
|
+
) });
|
|
2432
|
+
}
|
|
2433
|
+
if (rootMode) {
|
|
2434
|
+
return /* @__PURE__ */ jsx20(Box12, { minH: "100vh", bg: "gray.50", p: 8, children: /* @__PURE__ */ jsxs13(
|
|
2435
|
+
VStack3,
|
|
2436
|
+
{
|
|
2437
|
+
maxW: "560px",
|
|
2438
|
+
mx: "auto",
|
|
2439
|
+
bg: "white",
|
|
2440
|
+
p: 8,
|
|
2441
|
+
borderRadius: "lg",
|
|
2442
|
+
align: "stretch",
|
|
2443
|
+
gap: 5,
|
|
2444
|
+
children: [
|
|
2445
|
+
/* @__PURE__ */ jsx20(Heading8, { children: "Ativa\xE7\xE3o inicial do Ecossistema Oon" }),
|
|
2446
|
+
/* @__PURE__ */ jsx20(Text13, { color: "gray.600", children: "Esta \xE9 a aplica\xE7\xE3o root do ecossistema. Informe somente o e-mail e a senha do primeiro administrador. O Core criar\xE1 a estrutura m\xEDnima necess\xE1ria para liberar o primeiro login." }),
|
|
2447
|
+
/* @__PURE__ */ jsxs13(Text13, { fontSize: "sm", color: "gray.500", children: [
|
|
2448
|
+
"App: ",
|
|
2449
|
+
config.technical.appCode,
|
|
2450
|
+
" \xB7 Ambiente:",
|
|
2451
|
+
" ",
|
|
2452
|
+
config.technical.ambiente
|
|
2453
|
+
] }),
|
|
2454
|
+
error ? /* @__PURE__ */ jsx20(Text13, { color: "red.600", children: error }) : null,
|
|
2455
|
+
step !== 6 ? /* @__PURE__ */ jsxs13(Fragment2, { children: [
|
|
2456
|
+
/* @__PURE__ */ jsxs13(Box12, { w: "100%", children: [
|
|
2457
|
+
/* @__PURE__ */ jsx20(Text13, { fontWeight: "medium", children: "E-mail do administrador *" }),
|
|
2458
|
+
/* @__PURE__ */ jsx20(
|
|
2459
|
+
Input5,
|
|
2460
|
+
{
|
|
2461
|
+
type: "email",
|
|
2462
|
+
autoComplete: "email",
|
|
2463
|
+
value: rootEmail,
|
|
2464
|
+
onChange: (event) => setRootEmail(event.target.value),
|
|
2465
|
+
placeholder: "administrador@empresa.com.br"
|
|
2466
|
+
}
|
|
2467
|
+
)
|
|
2468
|
+
] }),
|
|
2469
|
+
/* @__PURE__ */ jsxs13(Box12, { w: "100%", children: [
|
|
2470
|
+
/* @__PURE__ */ jsx20(Text13, { fontWeight: "medium", children: "Senha *" }),
|
|
2471
|
+
/* @__PURE__ */ jsx20(
|
|
2472
|
+
Input5,
|
|
2473
|
+
{
|
|
2474
|
+
type: "password",
|
|
2475
|
+
minLength: 8,
|
|
2476
|
+
autoComplete: "new-password",
|
|
2477
|
+
value: rootPassword,
|
|
2478
|
+
onChange: (event) => setRootPassword(event.target.value),
|
|
2479
|
+
placeholder: "M\xEDnimo de 8 caracteres"
|
|
2480
|
+
}
|
|
2481
|
+
)
|
|
2482
|
+
] }),
|
|
2483
|
+
/* @__PURE__ */ jsx20(
|
|
2484
|
+
Button9,
|
|
2485
|
+
{
|
|
2486
|
+
colorPalette: "blue",
|
|
2487
|
+
onClick: activateRoot,
|
|
2488
|
+
loading: submitting,
|
|
2489
|
+
disabled: submitting || !rootEmail.trim() || rootPassword.length < 8 || !activation.details?.podeAtivar,
|
|
2490
|
+
children: "Criar ecossistema e ativar"
|
|
2491
|
+
}
|
|
2492
|
+
)
|
|
2493
|
+
] }) : /* @__PURE__ */ jsxs13(Fragment2, { children: [
|
|
2494
|
+
/* @__PURE__ */ jsx20(Heading8, { size: "md", children: "Ativa\xE7\xE3o conclu\xEDda" }),
|
|
2495
|
+
/* @__PURE__ */ jsx20(Text13, { children: "Usu\xE1rio, tenant, aplicativo, licen\xE7a e permiss\xF5es foram preparados. Redirecionando para o primeiro login..." })
|
|
2496
|
+
] })
|
|
2497
|
+
]
|
|
2498
|
+
}
|
|
2499
|
+
) });
|
|
2500
|
+
}
|
|
2501
|
+
return /* @__PURE__ */ jsx20(Box12, { minH: "100vh", bg: "gray.50", p: 8, children: /* @__PURE__ */ jsxs13(
|
|
2502
|
+
VStack3,
|
|
2503
|
+
{
|
|
2504
|
+
maxW: "760px",
|
|
2505
|
+
mx: "auto",
|
|
2506
|
+
bg: "white",
|
|
2507
|
+
p: 8,
|
|
2508
|
+
borderRadius: "lg",
|
|
2509
|
+
align: "stretch",
|
|
2510
|
+
gap: 5,
|
|
2511
|
+
children: [
|
|
2512
|
+
/* @__PURE__ */ jsx20(Heading8, { children: "Ativa\xE7\xE3o da Central Oon" }),
|
|
2513
|
+
/* @__PURE__ */ jsxs13(Text13, { children: [
|
|
2514
|
+
"App: ",
|
|
2515
|
+
config.technical.appCode,
|
|
2516
|
+
" \xB7 Ambiente:",
|
|
2517
|
+
" ",
|
|
2518
|
+
config.technical.ambiente,
|
|
2519
|
+
" \xB7 Central:",
|
|
2520
|
+
" ",
|
|
2521
|
+
config.technical.centralAtivacaoHost
|
|
2522
|
+
] }),
|
|
2523
|
+
error ? /* @__PURE__ */ jsx20(Text13, { color: "red.600", children: error }) : null,
|
|
2524
|
+
step === 1 && /* @__PURE__ */ jsxs13(Fragment2, { children: [
|
|
2525
|
+
/* @__PURE__ */ jsx20(Text13, { children: "Informe o c\xF3digo de ativa\xE7\xE3o para validar tenant, aplicativo e licen\xE7a antes de consumir o c\xF3digo." }),
|
|
2526
|
+
/* @__PURE__ */ jsx20(
|
|
2527
|
+
Input5,
|
|
2528
|
+
{
|
|
2529
|
+
"aria-label": "C\xF3digo de ativa\xE7\xE3o",
|
|
2530
|
+
value: code,
|
|
2531
|
+
onChange: (event) => setCode(event.target.value),
|
|
2532
|
+
placeholder: "OON-XXXX"
|
|
2533
|
+
}
|
|
2534
|
+
),
|
|
2535
|
+
/* @__PURE__ */ jsx20(
|
|
2536
|
+
Button9,
|
|
2537
|
+
{
|
|
2538
|
+
onClick: validateCode,
|
|
2539
|
+
loading: submitting,
|
|
2540
|
+
disabled: !code || submitting,
|
|
2541
|
+
children: "Validar c\xF3digo"
|
|
2542
|
+
}
|
|
2543
|
+
)
|
|
2544
|
+
] }),
|
|
2545
|
+
step === 2 && /* @__PURE__ */ jsxs13(Fragment2, { children: [
|
|
2546
|
+
/* @__PURE__ */ jsx20(Heading8, { size: "md", children: "Confirma\xE7\xE3o" }),
|
|
2547
|
+
/* @__PURE__ */ jsx20("pre", { children: JSON.stringify(preview, null, 2) }),
|
|
2548
|
+
/* @__PURE__ */ jsx20(Button9, { onClick: () => setStep(3), children: "Continuar" })
|
|
2549
|
+
] }),
|
|
2550
|
+
step === 3 && /* @__PURE__ */ jsxs13(Fragment2, { children: [
|
|
2551
|
+
config.globalFields.map(
|
|
2552
|
+
(field) => renderField(field, global, setGlobal)
|
|
2553
|
+
),
|
|
2554
|
+
/* @__PURE__ */ jsx20(Button9, { onClick: () => setStep(4), children: "Continuar" })
|
|
2555
|
+
] }),
|
|
2556
|
+
step === 4 && /* @__PURE__ */ jsxs13(Fragment2, { children: [
|
|
2557
|
+
config.appFields.length ? config.appFields.map((field) => renderField(field, app, setApp)) : /* @__PURE__ */ jsx20(Text13, { children: "Nenhum par\xE2metro adicional declarado." }),
|
|
2558
|
+
/* @__PURE__ */ jsx20(Button9, { onClick: () => setStep(5), children: "Revisar" })
|
|
2559
|
+
] }),
|
|
2560
|
+
step === 5 && /* @__PURE__ */ jsxs13(Fragment2, { children: [
|
|
2561
|
+
/* @__PURE__ */ jsx20(Text13, { children: "Ao confirmar, o c\xF3digo ser\xE1 consumido e a inst\xE2ncia ser\xE1 registrada no Ecossistema Oon." }),
|
|
2562
|
+
/* @__PURE__ */ jsx20(
|
|
2563
|
+
Button9,
|
|
2564
|
+
{
|
|
2565
|
+
colorPalette: "blue",
|
|
2566
|
+
onClick: completeMemberActivation,
|
|
2567
|
+
loading: submitting,
|
|
2568
|
+
disabled: submitting,
|
|
2569
|
+
children: "Concluir ativa\xE7\xE3o"
|
|
2570
|
+
}
|
|
2571
|
+
)
|
|
2572
|
+
] }),
|
|
2573
|
+
step === 6 && /* @__PURE__ */ jsxs13(Fragment2, { children: [
|
|
2574
|
+
/* @__PURE__ */ jsx20(Heading8, { size: "md", children: "Ativa\xE7\xE3o conclu\xEDda" }),
|
|
2575
|
+
/* @__PURE__ */ jsx20(Text13, { children: "Redirecionando para o login..." })
|
|
2576
|
+
] })
|
|
2577
|
+
]
|
|
2578
|
+
}
|
|
2579
|
+
) });
|
|
2580
|
+
}
|
|
2581
|
+
function InstanceBlockedPage({
|
|
2582
|
+
title,
|
|
2583
|
+
message
|
|
2584
|
+
}) {
|
|
2585
|
+
return /* @__PURE__ */ jsxs13(Box12, { p: 8, children: [
|
|
2586
|
+
/* @__PURE__ */ jsx20(Heading8, { children: title }),
|
|
2587
|
+
/* @__PURE__ */ jsx20(Text13, { children: message })
|
|
994
2588
|
] });
|
|
995
2589
|
}
|
|
996
2590
|
|
|
997
2591
|
// src/security/RouteGuard.tsx
|
|
998
|
-
import { Fragment, jsx as
|
|
2592
|
+
import { Fragment as Fragment3, jsx as jsx21 } from "react/jsx-runtime";
|
|
999
2593
|
function RouteGuard({
|
|
1000
2594
|
permission,
|
|
1001
2595
|
children
|
|
1002
2596
|
}) {
|
|
1003
2597
|
const { status, hasPermission } = useOonAuth();
|
|
1004
|
-
if (status === "loading") return /* @__PURE__ */
|
|
1005
|
-
if (status === "unauthenticated") return /* @__PURE__ */
|
|
2598
|
+
if (status === "loading") return /* @__PURE__ */ jsx21(LoadingScreen, {});
|
|
2599
|
+
if (status === "unauthenticated") return /* @__PURE__ */ jsx21(LoadingScreen, { message: "Redirecionando para o login..." });
|
|
1006
2600
|
if (!hasPermission(permission)) {
|
|
1007
|
-
return /* @__PURE__ */
|
|
2601
|
+
return /* @__PURE__ */ jsx21(LoadingScreen, { message: "Voc\xEA n\xE3o tem acesso a esta tela." });
|
|
1008
2602
|
}
|
|
1009
|
-
return /* @__PURE__ */
|
|
2603
|
+
return /* @__PURE__ */ jsx21(Fragment3, { children });
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
// src/security/ActivationGuard.tsx
|
|
2607
|
+
import { Navigate as Navigate3 } from "react-router-dom";
|
|
2608
|
+
import { jsx as jsx22 } from "react/jsx-runtime";
|
|
2609
|
+
function ActivationGuard({ children }) {
|
|
2610
|
+
const { status } = useActivation();
|
|
2611
|
+
if (status === "loading") return null;
|
|
2612
|
+
if (status === "suspended") return /* @__PURE__ */ jsx22(Navigate3, { to: "/instancia-suspensa", replace: true });
|
|
2613
|
+
if (status === "revoked") return /* @__PURE__ */ jsx22(Navigate3, { to: "/instancia-revogada", replace: true });
|
|
2614
|
+
if (status !== "active" && window.location.pathname !== "/ativacao") return /* @__PURE__ */ jsx22(Navigate3, { to: "/ativacao", replace: true });
|
|
2615
|
+
return children;
|
|
1010
2616
|
}
|
|
1011
2617
|
|
|
1012
2618
|
// src/app/createOonRouter.tsx
|
|
1013
|
-
import { jsx as
|
|
2619
|
+
import { jsx as jsx23 } from "react/jsx-runtime";
|
|
1014
2620
|
function createOonRouter({ app, menu, routes, security }) {
|
|
1015
2621
|
const guardEnabled = security?.enableRouteGuard !== false;
|
|
1016
|
-
const publicRoutes = routes.filter((
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1019
|
-
|
|
2622
|
+
const publicRoutes = routes.filter((route) => route.public).map((route) => ({ path: route.path, element: route.element }));
|
|
2623
|
+
const privateSource = routes.filter((route) => !route.public);
|
|
2624
|
+
const privateRoutes = privateSource.map((route) => {
|
|
2625
|
+
const element = guardEnabled ? /* @__PURE__ */ jsx23(RouteGuard, { permission: route.permissions, children: route.element }) : route.element;
|
|
2626
|
+
return route.path === "/" ? { index: true, element } : { path: route.path.replace(/^\//, ""), element };
|
|
1020
2627
|
});
|
|
2628
|
+
const hasIndexRoute = privateSource.some((route) => route.path === "/");
|
|
2629
|
+
const firstRoute = privateSource.find((route) => route.path !== "/");
|
|
2630
|
+
if (!hasIndexRoute && firstRoute) {
|
|
2631
|
+
privateRoutes.unshift({
|
|
2632
|
+
index: true,
|
|
2633
|
+
element: /* @__PURE__ */ jsx23(Navigate4, { to: firstRoute.path, replace: true })
|
|
2634
|
+
});
|
|
2635
|
+
}
|
|
2636
|
+
const customPaths = new Set(publicRoutes.map((route) => route.path));
|
|
2637
|
+
const builtInPublicRoutes = [];
|
|
2638
|
+
if (!customPaths.has("/login")) {
|
|
2639
|
+
builtInPublicRoutes.push({
|
|
2640
|
+
path: "/login",
|
|
2641
|
+
element: /* @__PURE__ */ jsx23(ActivationGuard, { children: /* @__PURE__ */ jsx23(LoginPage, { app }) })
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2644
|
+
if (!customPaths.has("/ativacao")) {
|
|
2645
|
+
builtInPublicRoutes.push({
|
|
2646
|
+
path: "/ativacao",
|
|
2647
|
+
element: /* @__PURE__ */ jsx23(ActivationPage, {})
|
|
2648
|
+
});
|
|
2649
|
+
}
|
|
2650
|
+
if (!customPaths.has("/instancia-suspensa")) {
|
|
2651
|
+
builtInPublicRoutes.push({
|
|
2652
|
+
path: "/instancia-suspensa",
|
|
2653
|
+
element: /* @__PURE__ */ jsx23(
|
|
2654
|
+
InstanceBlockedPage,
|
|
2655
|
+
{
|
|
2656
|
+
title: "Inst\xE2ncia suspensa",
|
|
2657
|
+
message: "Esta Central est\xE1 suspensa."
|
|
2658
|
+
}
|
|
2659
|
+
)
|
|
2660
|
+
});
|
|
2661
|
+
}
|
|
2662
|
+
if (!customPaths.has("/instancia-revogada")) {
|
|
2663
|
+
builtInPublicRoutes.push({
|
|
2664
|
+
path: "/instancia-revogada",
|
|
2665
|
+
element: /* @__PURE__ */ jsx23(
|
|
2666
|
+
InstanceBlockedPage,
|
|
2667
|
+
{
|
|
2668
|
+
title: "Inst\xE2ncia revogada",
|
|
2669
|
+
message: "Esta Central foi revogada."
|
|
2670
|
+
}
|
|
2671
|
+
)
|
|
2672
|
+
});
|
|
2673
|
+
}
|
|
2674
|
+
if (!customPaths.has("/acesso-negado")) {
|
|
2675
|
+
builtInPublicRoutes.push({
|
|
2676
|
+
path: "/acesso-negado",
|
|
2677
|
+
element: /* @__PURE__ */ jsx23(AccessDeniedPage, { app })
|
|
2678
|
+
});
|
|
2679
|
+
}
|
|
1021
2680
|
const tree = [
|
|
2681
|
+
...builtInPublicRoutes,
|
|
1022
2682
|
...publicRoutes,
|
|
1023
2683
|
{
|
|
1024
2684
|
path: "/",
|
|
1025
|
-
element: /* @__PURE__ */
|
|
2685
|
+
element: /* @__PURE__ */ jsx23(ActivationGuard, { children: /* @__PURE__ */ jsx23(AppAccessGuard, { children: /* @__PURE__ */ jsx23(AuthenticatedLayout, { app, menu }) }) }),
|
|
1026
2686
|
children: privateRoutes
|
|
1027
2687
|
}
|
|
1028
2688
|
];
|
|
@@ -1030,29 +2690,44 @@ function createOonRouter({ app, menu, routes, security }) {
|
|
|
1030
2690
|
}
|
|
1031
2691
|
|
|
1032
2692
|
// src/app/OonApp.tsx
|
|
1033
|
-
import { jsx as
|
|
2693
|
+
import { jsx as jsx24 } from "react/jsx-runtime";
|
|
2694
|
+
function localLoginUrl() {
|
|
2695
|
+
const current = `${window.location.pathname}${window.location.search}`;
|
|
2696
|
+
return `/login?redirect=${encodeURIComponent(current)}`;
|
|
2697
|
+
}
|
|
1034
2698
|
function OonApp({ config }) {
|
|
1035
|
-
const { http, storage, router } =
|
|
2699
|
+
const { http, storage, router } = useMemo4(() => {
|
|
1036
2700
|
const storage2 = createTokenStorage(config.app.id);
|
|
1037
|
-
const
|
|
2701
|
+
const externalLoginUrl = config.auth?.loginUrl ?? config.api.meusAppsUrl;
|
|
1038
2702
|
const http2 = createRestClient({
|
|
1039
2703
|
api: config.api,
|
|
1040
2704
|
appId: config.app.id,
|
|
1041
2705
|
getToken: () => storage2.get(),
|
|
1042
2706
|
onUnauthorized: () => {
|
|
1043
2707
|
storage2.clear();
|
|
1044
|
-
if (
|
|
2708
|
+
if (externalLoginUrl) {
|
|
2709
|
+
window.location.href = externalLoginUrl;
|
|
2710
|
+
return;
|
|
2711
|
+
}
|
|
2712
|
+
if (window.location.pathname !== "/login") {
|
|
2713
|
+
window.location.href = localLoginUrl();
|
|
2714
|
+
}
|
|
1045
2715
|
}
|
|
1046
2716
|
});
|
|
1047
2717
|
const { routes, menu } = resolveRegistry(config);
|
|
1048
|
-
const router2 = createOonRouter({
|
|
2718
|
+
const router2 = createOonRouter({
|
|
2719
|
+
app: config.app,
|
|
2720
|
+
menu,
|
|
2721
|
+
routes,
|
|
2722
|
+
security: config.security
|
|
2723
|
+
});
|
|
1049
2724
|
return { http: http2, storage: storage2, router: router2 };
|
|
1050
2725
|
}, [config]);
|
|
1051
|
-
return /* @__PURE__ */
|
|
2726
|
+
return /* @__PURE__ */ jsx24(OonProviders, { config, http, storage, children: /* @__PURE__ */ jsx24(RouterProvider, { router }) });
|
|
1052
2727
|
}
|
|
1053
2728
|
|
|
1054
2729
|
// src/start.tsx
|
|
1055
|
-
import { jsx as
|
|
2730
|
+
import { jsx as jsx25 } from "react/jsx-runtime";
|
|
1056
2731
|
function validateConfig(config) {
|
|
1057
2732
|
if (!config?.app?.id) throw new Error("oonCoreFront.start: config.app.id \xE9 obrigat\xF3rio.");
|
|
1058
2733
|
if (!config?.api?.baseUrl) throw new Error("oonCoreFront.start: config.api.baseUrl \xE9 obrigat\xF3rio.");
|
|
@@ -1086,7 +2761,7 @@ function start(config) {
|
|
|
1086
2761
|
const el = document.getElementById(rootId);
|
|
1087
2762
|
if (!el) throw new Error(`oonCoreFront.start: elemento #${rootId} n\xE3o encontrado no DOM.`);
|
|
1088
2763
|
createRoot(el).render(
|
|
1089
|
-
/* @__PURE__ */
|
|
2764
|
+
/* @__PURE__ */ jsx25(StrictMode, { children: /* @__PURE__ */ jsx25(OonApp, { config }) })
|
|
1090
2765
|
);
|
|
1091
2766
|
}
|
|
1092
2767
|
var oonCoreFront = { start };
|
|
@@ -1142,7 +2817,7 @@ function manifestToConfig(manifest, runtime) {
|
|
|
1142
2817
|
assistantBaseUrl: runtime.assistantBaseUrl,
|
|
1143
2818
|
versionPrefix: runtime.versionPrefix ?? ""
|
|
1144
2819
|
},
|
|
1145
|
-
auth: { mode: "bearer", tokenParam: "code" },
|
|
2820
|
+
auth: { mode: "bearer", tokenParam: "code", devToken: runtime.devToken },
|
|
1146
2821
|
security: {
|
|
1147
2822
|
enableRouteGuard: true,
|
|
1148
2823
|
enablePermissionGate: true,
|
|
@@ -1174,34 +2849,34 @@ function defineOonModule(mod) {
|
|
|
1174
2849
|
}
|
|
1175
2850
|
|
|
1176
2851
|
// src/components/CoreIntegration.tsx
|
|
1177
|
-
import { Badge, Box as
|
|
1178
|
-
import { useMutation as
|
|
1179
|
-
import { jsx as
|
|
2852
|
+
import { Badge as Badge3, Box as Box13, Button as Button10, Flex as Flex10, Heading as Heading9, Stack as Stack7, Text as Text14 } from "@chakra-ui/react";
|
|
2853
|
+
import { useMutation as useMutation3 } from "@tanstack/react-query";
|
|
2854
|
+
import { jsx as jsx26, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1180
2855
|
function CoreIntegration({ label = "Integra\xE7\xF5es" }) {
|
|
1181
2856
|
const { http } = useOonApi();
|
|
1182
2857
|
const metadata = useCoreMetadata();
|
|
1183
|
-
const reprocess =
|
|
2858
|
+
const reprocess = useMutation3({
|
|
1184
2859
|
mutationFn: () => http.post("/integrations/actions/process-active", {}, { headers: { "x-oon-origin": "integration" } })
|
|
1185
2860
|
});
|
|
1186
|
-
if (metadata.isLoading) return /* @__PURE__ */
|
|
1187
|
-
if (metadata.isError) return /* @__PURE__ */
|
|
2861
|
+
if (metadata.isLoading) return /* @__PURE__ */ jsx26(LoadingScreen, {});
|
|
2862
|
+
if (metadata.isError) return /* @__PURE__ */ jsx26(ErrorState, { error: metadata.error });
|
|
1188
2863
|
const integrations = metadata.data?.integrations ?? [];
|
|
1189
|
-
return /* @__PURE__ */
|
|
1190
|
-
/* @__PURE__ */
|
|
1191
|
-
/* @__PURE__ */
|
|
1192
|
-
/* @__PURE__ */
|
|
2864
|
+
return /* @__PURE__ */ jsxs14(Stack7, { gap: 6, children: [
|
|
2865
|
+
/* @__PURE__ */ jsxs14(Flex10, { align: "center", justify: "space-between", children: [
|
|
2866
|
+
/* @__PURE__ */ jsx26(Heading9, { size: "lg", children: label }),
|
|
2867
|
+
/* @__PURE__ */ jsx26(Button10, { size: "sm", variant: "outline", loading: reprocess.isPending, onClick: () => reprocess.mutate(), children: "Reprocessar ativos" })
|
|
1193
2868
|
] }),
|
|
1194
|
-
/* @__PURE__ */
|
|
1195
|
-
|
|
2869
|
+
/* @__PURE__ */ jsx26(Stack7, { gap: 3, children: integrations.length === 0 ? /* @__PURE__ */ jsx26(Text14, { color: "gray.400", children: "Nenhuma integra\xE7\xE3o declarada." }) : integrations.map((it, idx) => /* @__PURE__ */ jsx26(
|
|
2870
|
+
Box13,
|
|
1196
2871
|
{
|
|
1197
2872
|
borderWidth: "1px",
|
|
1198
2873
|
borderColor: "gray.200",
|
|
1199
2874
|
borderRadius: "md",
|
|
1200
2875
|
bg: "white",
|
|
1201
2876
|
p: 4,
|
|
1202
|
-
children: /* @__PURE__ */
|
|
1203
|
-
/* @__PURE__ */
|
|
1204
|
-
/* @__PURE__ */
|
|
2877
|
+
children: /* @__PURE__ */ jsxs14(Flex10, { align: "center", justify: "space-between", children: [
|
|
2878
|
+
/* @__PURE__ */ jsx26(Text14, { fontWeight: "medium", children: it.label ?? it.name ?? "Integra\xE7\xE3o" }),
|
|
2879
|
+
/* @__PURE__ */ jsx26(Badge3, { colorPalette: it.status === "erro" ? "red" : it.status === "ativo" ? "green" : "gray", children: it.status ?? "\u2014" })
|
|
1205
2880
|
] })
|
|
1206
2881
|
},
|
|
1207
2882
|
it.id ?? it.name ?? idx
|
|
@@ -1210,36 +2885,36 @@ function CoreIntegration({ label = "Integra\xE7\xF5es" }) {
|
|
|
1210
2885
|
}
|
|
1211
2886
|
|
|
1212
2887
|
// src/components/CoreCurrency.tsx
|
|
1213
|
-
import { Button as
|
|
1214
|
-
import { useMutation as
|
|
1215
|
-
import { jsx as
|
|
2888
|
+
import { Button as Button11, Flex as Flex11, Heading as Heading10, Stack as Stack8, Text as Text15 } from "@chakra-ui/react";
|
|
2889
|
+
import { useMutation as useMutation4, useQueryClient as useQueryClient3 } from "@tanstack/react-query";
|
|
2890
|
+
import { jsx as jsx27, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1216
2891
|
function CoreCurrency({ model = "Moeda", label = "Multi-moedas", endpoint = "/moedas", columns }) {
|
|
1217
2892
|
const { http } = useOonApi();
|
|
1218
|
-
const queryClient =
|
|
1219
|
-
const updateRates =
|
|
2893
|
+
const queryClient = useQueryClient3();
|
|
2894
|
+
const updateRates = useMutation4({
|
|
1220
2895
|
mutationFn: () => http.post(`${endpoint}/actions/update-rates`, {}, { headers: { "x-oon-origin": "integration" } }),
|
|
1221
2896
|
onSuccess: () => queryClient.invalidateQueries({ queryKey: ["oon", "list", endpoint] })
|
|
1222
2897
|
});
|
|
1223
|
-
return /* @__PURE__ */
|
|
1224
|
-
/* @__PURE__ */
|
|
1225
|
-
/* @__PURE__ */
|
|
1226
|
-
/* @__PURE__ */
|
|
2898
|
+
return /* @__PURE__ */ jsxs15(Stack8, { gap: 6, children: [
|
|
2899
|
+
/* @__PURE__ */ jsxs15(Flex11, { align: "center", justify: "space-between", children: [
|
|
2900
|
+
/* @__PURE__ */ jsx27(Heading10, { size: "lg", children: label }),
|
|
2901
|
+
/* @__PURE__ */ jsx27(Button11, { size: "sm", colorPalette: "blue", loading: updateRates.isPending, onClick: () => updateRates.mutate(), children: "Atualizar cota\xE7\xF5es" })
|
|
1227
2902
|
] }),
|
|
1228
|
-
updateRates.isError ? /* @__PURE__ */
|
|
1229
|
-
/* @__PURE__ */
|
|
2903
|
+
updateRates.isError ? /* @__PURE__ */ jsx27(Text15, { fontSize: "sm", color: "red.600", children: updateRates.error?.message }) : null,
|
|
2904
|
+
/* @__PURE__ */ jsx27(CoreCollection, { model, label: "", mode: columns ? "full" : "dynamic", endpoint, columns })
|
|
1230
2905
|
] });
|
|
1231
2906
|
}
|
|
1232
2907
|
|
|
1233
2908
|
// src/components/CoreAssistant.tsx
|
|
1234
|
-
import { useState as
|
|
1235
|
-
import { Box as
|
|
1236
|
-
import { useMutation as
|
|
1237
|
-
import { jsx as
|
|
2909
|
+
import { useState as useState10 } from "react";
|
|
2910
|
+
import { Box as Box14, Button as Button12, Flex as Flex12, Heading as Heading11, Input as Input6, Stack as Stack9, Text as Text16 } from "@chakra-ui/react";
|
|
2911
|
+
import { useMutation as useMutation5 } from "@tanstack/react-query";
|
|
2912
|
+
import { jsx as jsx28, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1238
2913
|
function CoreAssistant({ assistantCode, label = "Assistente", entity, endpoint = "/assistant-runs" }) {
|
|
1239
2914
|
const { http } = useOonApi();
|
|
1240
|
-
const [messages, setMessages] =
|
|
1241
|
-
const [input, setInput] =
|
|
1242
|
-
const ask =
|
|
2915
|
+
const [messages, setMessages] = useState10([]);
|
|
2916
|
+
const [input, setInput] = useState10("");
|
|
2917
|
+
const ask = useMutation5({
|
|
1243
2918
|
mutationFn: async (message) => {
|
|
1244
2919
|
const { data } = await http.post(
|
|
1245
2920
|
endpoint,
|
|
@@ -1263,13 +2938,13 @@ function CoreAssistant({ assistantCode, label = "Assistente", entity, endpoint =
|
|
|
1263
2938
|
setInput("");
|
|
1264
2939
|
ask.mutate(text);
|
|
1265
2940
|
};
|
|
1266
|
-
return /* @__PURE__ */
|
|
1267
|
-
/* @__PURE__ */
|
|
1268
|
-
/* @__PURE__ */
|
|
1269
|
-
messages.length === 0 ? /* @__PURE__ */
|
|
1270
|
-
|
|
2941
|
+
return /* @__PURE__ */ jsxs16(Stack9, { gap: 4, maxW: "560px", children: [
|
|
2942
|
+
/* @__PURE__ */ jsx28(Heading11, { size: "md", children: label }),
|
|
2943
|
+
/* @__PURE__ */ jsx28(Box14, { borderWidth: "1px", borderColor: "gray.200", borderRadius: "md", bg: "white", p: 4, minH: "240px", children: /* @__PURE__ */ jsxs16(Stack9, { gap: 3, children: [
|
|
2944
|
+
messages.length === 0 ? /* @__PURE__ */ jsx28(Text16, { color: "gray.400", fontSize: "sm", children: "Fa\xE7a uma pergunta ao assistente." }) : messages.map((m, idx) => /* @__PURE__ */ jsx28(Flex12, { justify: m.role === "user" ? "flex-end" : "flex-start", children: /* @__PURE__ */ jsx28(
|
|
2945
|
+
Box14,
|
|
1271
2946
|
{
|
|
1272
|
-
bg: m.role === "user" ? "
|
|
2947
|
+
bg: m.role === "user" ? "brand.500" : "gray.100",
|
|
1273
2948
|
color: m.role === "user" ? "white" : "gray.800",
|
|
1274
2949
|
px: 3,
|
|
1275
2950
|
py: 2,
|
|
@@ -1279,12 +2954,12 @@ function CoreAssistant({ assistantCode, label = "Assistente", entity, endpoint =
|
|
|
1279
2954
|
children: m.content
|
|
1280
2955
|
}
|
|
1281
2956
|
) }, idx)),
|
|
1282
|
-
ask.isPending ? /* @__PURE__ */
|
|
1283
|
-
ask.isError ? /* @__PURE__ */
|
|
2957
|
+
ask.isPending ? /* @__PURE__ */ jsx28(Text16, { color: "gray.400", fontSize: "sm", children: "Pensando\u2026" }) : null,
|
|
2958
|
+
ask.isError ? /* @__PURE__ */ jsx28(Text16, { color: "red.600", fontSize: "sm", children: ask.error?.message }) : null
|
|
1284
2959
|
] }) }),
|
|
1285
|
-
/* @__PURE__ */
|
|
1286
|
-
/* @__PURE__ */
|
|
1287
|
-
|
|
2960
|
+
/* @__PURE__ */ jsxs16(Flex12, { gap: 2, children: [
|
|
2961
|
+
/* @__PURE__ */ jsx28(
|
|
2962
|
+
Input6,
|
|
1288
2963
|
{
|
|
1289
2964
|
size: "sm",
|
|
1290
2965
|
value: input,
|
|
@@ -1295,20 +2970,20 @@ function CoreAssistant({ assistantCode, label = "Assistente", entity, endpoint =
|
|
|
1295
2970
|
}
|
|
1296
2971
|
}
|
|
1297
2972
|
),
|
|
1298
|
-
/* @__PURE__ */
|
|
2973
|
+
/* @__PURE__ */ jsx28(Button12, { size: "sm", colorPalette: "blue", onClick: send, loading: ask.isPending, children: "Enviar" })
|
|
1299
2974
|
] })
|
|
1300
2975
|
] });
|
|
1301
2976
|
}
|
|
1302
2977
|
|
|
1303
2978
|
// src/security/PermissionGate.tsx
|
|
1304
|
-
import { Fragment as
|
|
2979
|
+
import { Fragment as Fragment4, jsx as jsx29 } from "react/jsx-runtime";
|
|
1305
2980
|
function PermissionGate({
|
|
1306
2981
|
permission,
|
|
1307
2982
|
fallback = null,
|
|
1308
2983
|
children
|
|
1309
2984
|
}) {
|
|
1310
2985
|
const { hasPermission } = useOonAuth();
|
|
1311
|
-
return /* @__PURE__ */
|
|
2986
|
+
return /* @__PURE__ */ jsx29(Fragment4, { children: hasPermission(permission) ? children : fallback });
|
|
1312
2987
|
}
|
|
1313
2988
|
export {
|
|
1314
2989
|
CoreAssistant,
|