@oondemand/oon-core-front 0.2.0 → 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/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 useMemo3 } from "react";
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) config.headers.Authorization = `Bearer ${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
- if (normalized.status === 401) onUnauthorized?.();
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
  );
@@ -144,26 +148,55 @@ function createOonQueryClient() {
144
148
  import { createSystem, defaultConfig, defineConfig, mergeConfigs } from "@chakra-ui/react";
145
149
  var brandColors = {
146
150
  brand: {
147
- 25: { value: "#F5FBFF" },
148
- 50: { value: "#F0F7FF" },
149
- 75: { value: "#E6F2FF" },
150
- 100: { value: "#C7E3FF" },
151
- 200: { value: "#6CBCE8" },
151
+ 25: { value: "#F8FCFF" },
152
+ 50: { value: "#EEF8FD" },
153
+ 75: { value: "#E2F3FB" },
154
+ 100: { value: "#C7E8F7" },
155
+ 200: { value: "#82C8E8" },
152
156
  300: { value: "#3FA6E0" },
153
157
  350: { value: "#0F8DD0" },
154
158
  400: { value: "#0580B9" },
155
159
  500: { value: "#0474AF" },
156
160
  600: { value: "#036491" },
157
161
  700: { value: "#014364" },
158
- 800: { value: "#034167" },
159
- 850: { value: "#021D42" },
160
- 900: { value: "#011225" }
162
+ 800: { value: "#073A52" },
163
+ 850: { value: "#082F3F" },
164
+ 900: { value: "#07262E" }
161
165
  }
162
166
  };
163
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
+ },
164
187
  theme: {
165
188
  tokens: {
166
- colors: brandColors
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
+ }
167
200
  },
168
201
  semanticTokens: {
169
202
  colors: {
@@ -328,55 +361,212 @@ function useModelSchema(name) {
328
361
  }
329
362
 
330
363
  // src/security/AuthProvider.tsx
331
- import { createContext as createContext2, useContext as useContext2, useEffect, useState, useCallback } from "react";
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";
332
380
  import { jsx as jsx2 } from "react/jsx-runtime";
333
- var AuthContext = createContext2(null);
334
- function AuthProvider({ http, storage, auth, loginUrl, children }) {
335
- const tokenParam = auth?.tokenParam ?? "code";
336
- const [user, setUser] = useState(null);
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
+ }) {
337
394
  const [status, setStatus] = useState("loading");
338
- const redirectToLogin = useCallback(() => {
339
- if (loginUrl) {
340
- window.location.href = loginUrl;
341
- } else {
342
- setStatus("unauthenticated");
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");
343
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;
344
450
  }, [loginUrl]);
345
- const logout = useCallback(() => {
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(() => {
346
517
  storage.clear();
347
518
  setUser(null);
348
- redirectToLogin();
349
- }, [storage, redirectToLogin]);
350
- useEffect(() => {
519
+ setStatus("unauthenticated");
520
+ redirectToExternalLogin();
521
+ }, [storage, redirectToExternalLogin]);
522
+ useEffect2(() => {
351
523
  let active = true;
524
+ if (activation && !["active", "error"].includes(activation.status)) {
525
+ setUser(null);
526
+ setStatus("unauthenticated");
527
+ return;
528
+ }
352
529
  const incoming = captureTokenFromUrl(tokenParam);
353
- if (incoming) storage.set(incoming);
354
- const token = storage.get();
530
+ const stored = storage.get();
531
+ const token = incoming ?? devToken ?? stored;
532
+ if (token && token !== stored) storage.set(token);
355
533
  if (!token) {
356
- redirectToLogin();
534
+ setUser(null);
535
+ setStatus("unauthenticated");
536
+ redirectToExternalLogin();
357
537
  return;
358
538
  }
359
- http.get("/auth/validar-token").then((res) => {
539
+ setStatus("loading");
540
+ http.get("/auth/validar-token").then((response) => {
360
541
  if (!active) return;
361
- setUser(res.data.usuario);
542
+ setUser(response.data.usuario);
362
543
  setStatus("authenticated");
363
- }).catch(() => {
544
+ }).catch((error) => {
364
545
  if (!active) return;
365
- storage.clear();
366
- redirectToLogin();
546
+ applyValidationError(error);
367
547
  });
368
548
  return () => {
369
549
  active = false;
370
550
  };
371
- }, [http, storage, tokenParam, redirectToLogin]);
551
+ }, [
552
+ http,
553
+ storage,
554
+ tokenParam,
555
+ devToken,
556
+ redirectToExternalLogin,
557
+ applyValidationError,
558
+ activation?.status
559
+ ]);
372
560
  const roles = user?.tipo ? [user.tipo] : [];
373
- const hasPermission = useCallback(
561
+ const hasPermission = useCallback2(
374
562
  (permission) => {
375
563
  if (!permission) return true;
376
564
  const required = Array.isArray(permission) ? permission : [permission];
377
565
  if (required.length === 0) return true;
378
566
  if (roles.includes("admin") || roles.includes("master")) return true;
379
- return required.some((p) => roles.includes(p) || roles.some((r) => p.startsWith(`${r}.`)));
567
+ return required.some(
568
+ (value2) => roles.includes(value2) || roles.some((role) => value2.startsWith(`${role}.`))
569
+ );
380
570
  },
381
571
  [roles]
382
572
  );
@@ -386,28 +576,34 @@ function AuthProvider({ http, storage, auth, loginUrl, children }) {
386
576
  status,
387
577
  isAuthenticated: status === "authenticated",
388
578
  hasPermission,
579
+ login,
580
+ retry,
389
581
  logout
390
582
  };
391
- return /* @__PURE__ */ jsx2(AuthContext.Provider, { value, children });
583
+ return /* @__PURE__ */ jsx3(AuthContext.Provider, { value, children });
392
584
  }
393
585
  function useOonAuth() {
394
- const ctx = useContext2(AuthContext);
395
- if (!ctx) throw new Error("useOonAuth deve ser usado dentro de <AuthProvider> (oonCoreFront.start).");
396
- return ctx;
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;
397
593
  }
398
594
 
399
595
  // src/app/OonProviders.tsx
400
- import { jsx as jsx3 } from "react/jsx-runtime";
596
+ import { jsx as jsx4 } from "react/jsx-runtime";
401
597
  function OonProviders({ config, http, storage, children }) {
402
598
  const queryClient = useMemo2(() => createOonQueryClient(), []);
403
599
  const system = useMemo2(() => createOonSystem(config.theme), [config.theme]);
404
600
  const loginUrl = config.auth?.loginUrl ?? config.api.meusAppsUrl;
405
- return /* @__PURE__ */ jsx3(QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsx3(ChakraProvider, { value: system, children: /* @__PURE__ */ jsx3(ApiProvider, { http, children: /* @__PURE__ */ jsx3(AuthProvider, { http, storage, auth: config.auth, loginUrl, children }) }) }) });
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 }) }) }) }) });
406
602
  }
407
603
 
408
604
  // src/components/CoreCollection.tsx
409
- import { useState as useState4 } from "react";
410
- import { Box as Box4, Heading as Heading3, Stack as Stack2 } from "@chakra-ui/react";
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";
411
607
  import { useMutation, useQueryClient } from "@tanstack/react-query";
412
608
 
413
609
  // src/components/internal/fieldUtils.ts
@@ -417,17 +613,39 @@ function isEditableField(meta) {
417
613
  }
418
614
  function fieldLabel(meta) {
419
615
  if (meta.label) return meta.label;
420
- return meta.name.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (c) => c.toUpperCase());
616
+ return meta.name.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (character) => character.toUpperCase());
421
617
  }
422
618
  function columnsFromMeta(fields) {
423
- return fields.filter(isEditableField).slice(0, 8).map((f) => ({ field: f.name, label: fieldLabel(f), kind: f.kind, sortable: true }));
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;
424
640
  }
425
641
  function formFieldsFromMeta(fields) {
426
- return fields.filter(isEditableField).map((f) => ({
427
- field: f.name,
428
- label: fieldLabel(f),
429
- kind: f.kind,
430
- options: f.options
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
431
649
  }));
432
650
  }
433
651
  function formatCell(value, kind) {
@@ -446,8 +664,8 @@ function formatCell(value, kind) {
446
664
  return new Intl.NumberFormat("pt-BR", { style: "currency", currency: "BRL" }).format(Number(value));
447
665
  case "ref":
448
666
  if (value && typeof value === "object") {
449
- const obj = value;
450
- return String(obj.nome ?? obj.label ?? obj.descricao ?? obj._id ?? "\u2014");
667
+ const object = value;
668
+ return String(object.nome ?? object.label ?? object.descricao ?? object._id ?? "\u2014");
451
669
  }
452
670
  return String(value);
453
671
  default:
@@ -456,30 +674,35 @@ function formatCell(value, kind) {
456
674
  }
457
675
 
458
676
  // src/components/internal/DataGrid.tsx
459
- import { useState as useState2 } from "react";
460
- 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";
461
679
  import { keepPreviousData, useQuery as useQuery2 } from "@tanstack/react-query";
462
680
 
463
681
  // src/shell/LoadingScreen.tsx
464
- import { Flex, Spinner, Text } from "@chakra-ui/react";
465
- import { jsx as jsx4, jsxs } from "react/jsx-runtime";
466
- function LoadingScreen({ message }) {
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
+ }) {
467
689
  return /* @__PURE__ */ jsxs(Flex, { direction: "column", align: "center", justify: "center", minH: "60vh", gap: 4, children: [
468
- /* @__PURE__ */ jsx4(Spinner, { size: "lg" }),
469
- message ? /* @__PURE__ */ jsx4(Text, { color: "gray.500", fontSize: "sm", children: message }) : null
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
470
693
  ] });
471
694
  }
472
695
 
473
696
  // src/shell/ErrorState.tsx
474
697
  import { Box, Heading, Text as Text2 } from "@chakra-ui/react";
475
- import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
698
+ import { jsx as jsx6, jsxs as jsxs2 } from "react/jsx-runtime";
476
699
  function ErrorState({
477
700
  error,
478
701
  title = "N\xE3o foi poss\xEDvel carregar"
479
702
  }) {
480
703
  return /* @__PURE__ */ jsxs2(Box, { borderWidth: "1px", borderColor: "red.200", bg: "red.50", borderRadius: "md", p: 4, children: [
481
- /* @__PURE__ */ jsx5(Heading, { size: "sm", color: "red.700", children: title }),
482
- /* @__PURE__ */ jsx5(Text2, { mt: 1, fontSize: "sm", color: "red.600", children: error?.message ?? "Erro inesperado." }),
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." }),
483
706
  error?.requestId ? /* @__PURE__ */ jsxs2(Text2, { mt: 2, fontSize: "xs", color: "red.400", children: [
484
707
  "requestId: ",
485
708
  error.requestId
@@ -488,8 +711,44 @@ function ErrorState({
488
711
  }
489
712
 
490
713
  // src/components/internal/DataGrid.tsx
491
- import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
492
- var PAGE_SIZE = 20;
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
+ }
493
752
  function DataGrid({
494
753
  resource,
495
754
  resourceKey,
@@ -500,104 +759,544 @@ function DataGrid({
500
759
  onDelete,
501
760
  toolbarExtra
502
761
  }) {
503
- const [pageIndex, setPageIndex] = useState2(0);
504
- const [searchTerm, setSearchTerm] = useState2("");
505
- const [sort, setSort] = useState2(void 0);
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);
506
767
  const query = useQuery2({
507
- queryKey: ["oon", "list", resourceKey, { pageIndex, searchTerm, sort }],
508
- queryFn: () => resource.list({ pageIndex, pageSize: PAGE_SIZE, searchTerm: searchTerm || void 0, sort }),
768
+ queryKey: ["oon", "list", resourceKey, { pageIndex, pageSize, searchTerm: deferredSearch, sort }],
769
+ queryFn: () => resource.list({ pageIndex, pageSize, searchTerm: deferredSearch || void 0, sort }),
509
770
  placeholderData: keepPreviousData
510
771
  });
511
772
  const toggleSort = (field) => {
512
773
  setPageIndex(0);
513
- setSort((prev) => {
514
- if (prev === `${field}.asc`) return `${field}.desc`;
515
- if (prev === `${field}.desc`) return void 0;
774
+ setSort((previous) => {
775
+ if (previous === `${field}.asc`) return `${field}.desc`;
776
+ if (previous === `${field}.desc`) return void 0;
516
777
  return `${field}.asc`;
517
778
  });
518
779
  };
519
780
  const pagination = query.data?.pagination;
520
781
  const rows = query.data?.results ?? [];
521
- return /* @__PURE__ */ jsxs3(Box2, { children: [
522
- /* @__PURE__ */ jsxs3(Flex2, { mb: 4, gap: 3, align: "center", children: [
523
- search ? /* @__PURE__ */ jsx6(
524
- Input,
525
- {
526
- size: "sm",
527
- maxW: "320px",
528
- placeholder: "Buscar...",
529
- value: searchTerm,
530
- onChange: (e) => {
531
- setPageIndex(0);
532
- setSearchTerm(e.target.value);
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" }
533
808
  }
534
- }
535
- ) : null,
536
- /* @__PURE__ */ jsx6(Spacer, {}),
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
+ ] }),
537
820
  toolbarExtra,
538
- onCreate ? /* @__PURE__ */ jsx6(Button, { size: "sm", colorPalette: "blue", onClick: onCreate, children: "Novo" }) : null
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
539
825
  ] }),
540
- query.isLoading ? /* @__PURE__ */ jsx6(LoadingScreen, {}) : query.isError ? /* @__PURE__ */ jsx6(ErrorState, { error: query.error }) : /* @__PURE__ */ jsx6(Box2, { borderWidth: "1px", borderColor: "gray.200", borderRadius: "md", overflowX: "auto", bg: "white", children: /* @__PURE__ */ jsxs3(Table.Root, { size: "sm", interactive: true, children: [
541
- /* @__PURE__ */ jsx6(Table.Header, { children: /* @__PURE__ */ jsxs3(Table.Row, { children: [
542
- columns.map((col) => /* @__PURE__ */ jsxs3(
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(
543
829
  Table.ColumnHeader,
544
830
  {
545
- cursor: col.sortable ? "pointer" : void 0,
546
- onClick: col.sortable ? () => toggleSort(col.field) : void 0,
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,
547
844
  children: [
548
- col.label ?? col.field,
549
- sort?.startsWith(`${col.field}.`) ? sort.endsWith(".asc") ? " \u25B2" : " \u25BC" : ""
845
+ column.label ?? column.field,
846
+ sort?.startsWith(`${column.field}.`) ? sort.endsWith(".asc") ? " \u25B2" : " \u25BC" : ""
550
847
  ]
551
848
  },
552
- col.field
849
+ column.field
553
850
  )),
554
- onEdit || onDelete ? /* @__PURE__ */ jsx6(Table.ColumnHeader, { textAlign: "end", children: "A\xE7\xF5es" }) : null
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
555
852
  ] }) }),
556
- /* @__PURE__ */ jsx6(Table.Body, { children: rows.length === 0 ? /* @__PURE__ */ jsx6(Table.Row, { children: /* @__PURE__ */ jsx6(Table.Cell, { colSpan: columns.length + 1, children: /* @__PURE__ */ jsx6(Text3, { color: "gray.400", py: 6, textAlign: "center", children: "Nenhum registro encontrado." }) }) }) : rows.map((row, idx) => /* @__PURE__ */ jsxs3(Table.Row, { children: [
557
- columns.map((col) => /* @__PURE__ */ jsx6(Table.Cell, { children: formatCell(row[col.field], col.kind) }, col.field)),
558
- onEdit || onDelete ? /* @__PURE__ */ jsx6(Table.Cell, { textAlign: "end", children: /* @__PURE__ */ jsxs3(Flex2, { gap: 2, justify: "end", children: [
559
- onEdit ? /* @__PURE__ */ jsx6(Button, { size: "xs", variant: "ghost", onClick: () => onEdit(row), children: "Editar" }) : null,
560
- onDelete ? /* @__PURE__ */ jsx6(Button, { size: "xs", variant: "ghost", colorPalette: "red", onClick: () => onDelete(row), children: "Excluir" }) : null
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
561
858
  ] }) }) : null
562
- ] }, row._id ?? idx)) })
859
+ ] }, row._id ?? index)) })
563
860
  ] }) }),
564
- pagination ? /* @__PURE__ */ jsxs3(Flex2, { mt: 4, align: "center", gap: 3, children: [
565
- /* @__PURE__ */ jsxs3(Text3, { fontSize: "sm", color: "gray.500", children: [
566
- pagination.totalItems,
567
- " registro(s) \xB7 p\xE1gina ",
568
- pagination.currentPage,
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,
569
890
  " de ",
570
- pagination.totalPages
891
+ totalPages
571
892
  ] }),
572
- /* @__PURE__ */ jsx6(Spacer, {}),
573
- /* @__PURE__ */ jsx6(Button, { size: "xs", variant: "outline", disabled: pageIndex <= 0, onClick: () => setPageIndex((p) => p - 1), children: "Anterior" }),
574
- /* @__PURE__ */ jsx6(
575
- Button,
576
- {
577
- size: "xs",
578
- variant: "outline",
579
- disabled: pagination.currentPage >= pagination.totalPages,
580
- onClick: () => setPageIndex((p) => p + 1),
581
- children: "Pr\xF3xima"
582
- }
583
- )
584
- ] }) : 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
+ ] })
585
898
  ] });
586
899
  }
587
900
 
588
901
  // src/components/internal/DynamicForm.tsx
589
- import { useState as useState3 } from "react";
590
- import { Box as Box3, Button as Button2, Flex as Flex3, Heading as Heading2, Input as Input2, Stack, Text as Text4, NativeSelect } from "@chakra-ui/react";
591
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
592
- function coerce(value, kind) {
593
- if (value === "") return void 0;
594
- if (kind === "number" || kind === "currency" || kind === "currencyConverted") return Number(value);
595
- if (kind === "boolean") return value === "true";
596
- return value;
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
+ );
597
973
  }
598
- function toInputValue(value, kind) {
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
+ ] });
1275
+ }
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) {
599
1298
  if (value == null) return "";
600
- if (kind === "date" && value) {
1299
+ if (kind === "date") {
601
1300
  try {
602
1301
  return new Date(value).toISOString().slice(0, 10);
603
1302
  } catch {
@@ -605,101 +1304,221 @@ function toInputValue(value, kind) {
605
1304
  }
606
1305
  }
607
1306
  if (typeof value === "object") {
608
- const obj = value;
609
- return String(obj._id ?? "");
1307
+ const record = value;
1308
+ return String(record._id ?? record.id ?? "");
610
1309
  }
611
1310
  return String(value);
612
1311
  }
613
- function DynamicForm({ title, fields, initialValues, submitting, error, onSubmit, onCancel }) {
614
- const [values, setValues] = useState3(() => {
615
- const init = {};
616
- for (const f of fields) init[f.field] = toInputValue(initialValues?.[f.field], f.kind);
617
- return init;
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.`;
618
1322
  });
619
- const setField = (name, value) => setValues((prev) => ({ ...prev, [name]: value }));
620
- const handleSubmit = (e) => {
621
- e.preventDefault();
622
- const payload = {};
623
- for (const f of fields) {
624
- const coerced = coerce(values[f.field] ?? "", f.kind);
625
- if (coerced !== void 0) payload[f.field] = coerced;
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;
626
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({});
627
1443
  onSubmit(payload);
628
1444
  };
629
- return /* @__PURE__ */ jsxs4(Box3, { borderWidth: "1px", borderColor: "gray.200", borderRadius: "md", bg: "white", p: 6, maxW: "640px", children: [
630
- /* @__PURE__ */ jsx7(Heading2, { size: "md", mb: 4, children: title }),
631
- /* @__PURE__ */ jsx7("form", { onSubmit: handleSubmit, children: /* @__PURE__ */ jsxs4(Stack, { gap: 4, children: [
632
- fields.map((f) => {
633
- const label = fieldLabel({ name: f.field, label: f.label });
634
- if (f.kind === "enum" && f.options?.length) {
635
- return /* @__PURE__ */ jsxs4(Box3, { children: [
636
- /* @__PURE__ */ jsx7(Text4, { fontSize: "sm", mb: 1, fontWeight: "medium", children: label }),
637
- /* @__PURE__ */ jsxs4(NativeSelect.Root, { size: "sm", children: [
638
- /* @__PURE__ */ jsxs4(
639
- NativeSelect.Field,
640
- {
641
- value: values[f.field] ?? "",
642
- onChange: (e) => setField(f.field, e.currentTarget.value),
643
- children: [
644
- /* @__PURE__ */ jsx7("option", { value: "", children: "\u2014" }),
645
- f.options.map((opt) => /* @__PURE__ */ jsx7("option", { value: opt, children: opt }, opt))
646
- ]
647
- }
648
- ),
649
- /* @__PURE__ */ jsx7(NativeSelect.Indicator, {})
650
- ] })
651
- ] }, f.field);
652
- }
653
- if (f.kind === "boolean") {
654
- return /* @__PURE__ */ jsxs4(Box3, { children: [
655
- /* @__PURE__ */ jsx7(Text4, { fontSize: "sm", mb: 1, fontWeight: "medium", children: label }),
656
- /* @__PURE__ */ jsxs4(NativeSelect.Root, { size: "sm", children: [
657
- /* @__PURE__ */ jsxs4(
658
- NativeSelect.Field,
659
- {
660
- value: values[f.field] ?? "",
661
- onChange: (e) => setField(f.field, e.currentTarget.value),
662
- children: [
663
- /* @__PURE__ */ jsx7("option", { value: "", children: "\u2014" }),
664
- /* @__PURE__ */ jsx7("option", { value: "true", children: "Sim" }),
665
- /* @__PURE__ */ jsx7("option", { value: "false", children: "N\xE3o" })
666
- ]
667
- }
668
- ),
669
- /* @__PURE__ */ jsx7(NativeSelect.Indicator, {})
670
- ] })
671
- ] }, f.field);
672
- }
673
- const inputType = f.kind === "date" ? "date" : f.kind === "number" || f.kind === "currency" || f.kind === "currencyConverted" ? "number" : "text";
674
- return /* @__PURE__ */ jsxs4(Box3, { children: [
675
- /* @__PURE__ */ jsxs4(Text4, { fontSize: "sm", mb: 1, fontWeight: "medium", children: [
676
- label,
677
- 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:" })
678
1463
  ] }),
679
- /* @__PURE__ */ jsx7(
680
- Input2,
681
- {
682
- size: "sm",
683
- type: inputType,
684
- required: f.required,
685
- value: values[f.field] ?? "",
686
- onChange: (e) => setField(f.field, e.currentTarget.value)
687
- }
688
- )
689
- ] }, f.field);
690
- }),
691
- error ? /* @__PURE__ */ jsx7(Text4, { fontSize: "sm", color: "red.600", children: error }) : null,
692
- /* @__PURE__ */ jsxs4(Flex3, { gap: 3, justify: "end", mt: 2, children: [
693
- /* @__PURE__ */ jsx7(Button2, { size: "sm", variant: "ghost", type: "button", onClick: onCancel, children: "Cancelar" }),
694
- /* @__PURE__ */ jsx7(Button2, { size: "sm", colorPalette: "blue", type: "submit", loading: submitting, children: "Salvar" })
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
+ ] })
695
1514
  ] })
696
- ] }) })
697
- ] });
1515
+ ] })
1516
+ ] }) });
698
1517
  }
699
1518
 
700
1519
  // src/components/CoreCollection.tsx
701
- import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
702
- 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 }) {
703
1522
  const needsSchema = mode !== "full" || !columns || !form || !endpoint;
704
1523
  const schemaQuery = useModelSchema(needsSchema ? model : "");
705
1524
  const schema = schemaQuery.data;
@@ -708,8 +1527,8 @@ function CoreCollection({ model, label, mode = "dynamic", endpoint, columns, for
708
1527
  const resolvedForm = form ?? (schema ? formFieldsFromMeta(schema.fields) : []);
709
1528
  const resource = useOonResource(basePath);
710
1529
  const queryClient = useQueryClient();
711
- const [formState, setFormState] = useState4({ open: false });
712
- const [formError, setFormError] = useState4(null);
1530
+ const [formState, setFormState] = useState6({ open: false });
1531
+ const [formError, setFormError] = useState6(null);
713
1532
  const invalidate = () => queryClient.invalidateQueries({ queryKey: ["oon", "list", basePath] });
714
1533
  const saveMutation = useMutation({
715
1534
  mutationFn: (values) => {
@@ -722,54 +1541,68 @@ function CoreCollection({ model, label, mode = "dynamic", endpoint, columns, for
722
1541
  setFormError(null);
723
1542
  invalidate();
724
1543
  },
725
- onError: (err) => setFormError(err.message)
1544
+ onError: (error) => setFormError(error)
726
1545
  });
727
1546
  const deleteMutation = useMutation({
728
1547
  mutationFn: (row) => resource.delete(row._id),
729
1548
  onSuccess: invalidate
730
1549
  });
731
- if (needsSchema && schemaQuery.isLoading) return /* @__PURE__ */ jsx8(LoadingScreen, {});
732
- if (needsSchema && schemaQuery.isError) return /* @__PURE__ */ jsx8(ErrorState, { error: schemaQuery.error });
1550
+ if (needsSchema && schemaQuery.isLoading) return /* @__PURE__ */ jsx10(LoadingScreen, {});
1551
+ if (needsSchema && schemaQuery.isError) return /* @__PURE__ */ jsx10(ErrorState, { error: schemaQuery.error });
733
1552
  const title = label ?? schema?.name ?? model;
734
- return /* @__PURE__ */ jsxs5(Stack2, { gap: 6, children: [
735
- /* @__PURE__ */ jsx8(Heading3, { size: "lg", children: title }),
736
- formState.open ? /* @__PURE__ */ jsx8(
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(
737
1579
  DynamicForm,
738
1580
  {
739
1581
  title: formState.row ? `Editar ${title}` : `Novo ${title}`,
1582
+ eyebrow: section,
740
1583
  fields: resolvedForm,
741
1584
  initialValues: formState.row,
742
1585
  submitting: saveMutation.isPending,
743
1586
  error: formError,
744
- onSubmit: (values) => saveMutation.mutate(values),
1587
+ onSubmit: (values) => {
1588
+ setFormError(null);
1589
+ saveMutation.mutate(values);
1590
+ },
1591
+ onFieldChange: () => setFormError(null),
745
1592
  onCancel: () => {
746
1593
  setFormState({ open: false });
747
1594
  setFormError(null);
748
1595
  }
749
1596
  }
750
- ) : /* @__PURE__ */ jsxs5(Box4, { children: [
751
- /* @__PURE__ */ jsx8(
752
- DataGrid,
753
- {
754
- resource,
755
- resourceKey: basePath,
756
- columns: resolvedColumns,
757
- onCreate: () => setFormState({ open: true }),
758
- onEdit: (row) => setFormState({ open: true, row }),
759
- onDelete: (row) => {
760
- if (window.confirm("Excluir este registro?")) deleteMutation.mutate(row);
761
- }
762
- }
763
- ),
764
- importExport ? null : null
765
- ] })
1597
+ ) : null,
1598
+ importExport ? null : null
766
1599
  ] });
767
1600
  }
768
1601
 
769
1602
  // src/components/CoreDocument.tsx
770
- import { jsx as jsx9 } from "react/jsx-runtime";
1603
+ import { jsx as jsx11 } from "react/jsx-runtime";
771
1604
  function CoreDocument({ model, label, endpoint, columns, form }) {
772
- return /* @__PURE__ */ jsx9(
1605
+ return /* @__PURE__ */ jsx11(
773
1606
  CoreCollection,
774
1607
  {
775
1608
  model,
@@ -783,25 +1616,48 @@ function CoreDocument({ model, label, endpoint, columns, form }) {
783
1616
  }
784
1617
 
785
1618
  // src/components/CorePipeline.tsx
786
- import { Box as Box5, Flex as Flex4, Heading as Heading4, Stack as Stack3, Text as Text5 } from "@chakra-ui/react";
787
- import { useQuery as useQuery3 } from "@tanstack/react-query";
788
- import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
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
+ }
789
1627
  function CorePipeline({ model, label, endpoint, stageField = "status", stages, titleField }) {
790
1628
  const schemaQuery = useModelSchema(model);
791
1629
  const schema = schemaQuery.data;
792
1630
  const basePath = endpoint ?? schema?.basePath ?? `/${model.toLowerCase()}s`;
793
1631
  const resource = useOonResource(basePath);
794
- const listQuery = useQuery3({
1632
+ const queryClient = useQueryClient2();
1633
+ const [selectedTicket, setSelectedTicket] = useState7(null);
1634
+ const [formError, setFormError] = useState7(null);
1635
+ const listQuery = useQuery4({
795
1636
  queryKey: ["oon", "pipeline", basePath],
796
1637
  queryFn: () => resource.list({ pageSize: 200 }),
797
1638
  enabled: !!schema
798
1639
  });
799
- if (schemaQuery.isLoading || listQuery.isLoading) return /* @__PURE__ */ jsx10(LoadingScreen, {});
800
- if (schemaQuery.isError) return /* @__PURE__ */ jsx10(ErrorState, { error: schemaQuery.error });
801
- if (listQuery.isError) return /* @__PURE__ */ jsx10(ErrorState, { error: listQuery.error });
802
- const stageFieldMeta = schema?.fields.find((f) => f.name === stageField);
803
- const resolvedStages = stages ?? (stageFieldMeta?.options ?? []).map((opt) => ({ id: opt, label: opt })) ?? [];
804
- const title = titleField ?? schema?.fields.find((f) => f.kind === "string")?.name ?? schema?.searchable[0] ?? "_id";
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 ?? []);
805
1661
  const rows = listQuery.data?.results ?? [];
806
1662
  const byStage = /* @__PURE__ */ new Map();
807
1663
  for (const stage of resolvedStages) byStage.set(stage.id, []);
@@ -810,25 +1666,101 @@ function CorePipeline({ model, label, endpoint, stageField = "status", stages, t
810
1666
  if (!byStage.has(key)) byStage.set(key, []);
811
1667
  byStage.get(key).push(row);
812
1668
  }
813
- return /* @__PURE__ */ jsxs6(Stack3, { gap: 6, children: [
814
- /* @__PURE__ */ jsx10(Heading4, { size: "lg", children: label ?? schema?.name ?? model }),
815
- /* @__PURE__ */ jsx10(Flex4, { gap: 4, align: "stretch", overflowX: "auto", pb: 2, children: Array.from(byStage.entries()).map(([stageId, items]) => {
816
- const stageLabel = resolvedStages.find((s) => s.id === stageId)?.label ?? (stageId || "\u2014");
817
- return /* @__PURE__ */ jsxs6(Box5, { minW: "280px", bg: "gray.100", borderRadius: "md", p: 3, children: [
818
- /* @__PURE__ */ jsxs6(Flex4, { align: "center", justify: "space-between", mb: 3, children: [
819
- /* @__PURE__ */ jsx10(Text5, { fontWeight: "semibold", fontSize: "sm", children: stageLabel }),
820
- /* @__PURE__ */ jsx10(Text5, { fontSize: "xs", color: "gray.500", children: items.length })
821
- ] }),
822
- /* @__PURE__ */ jsx10(Stack3, { gap: 2, children: items.map((row, idx) => /* @__PURE__ */ jsx10(Box5, { bg: "white", borderRadius: "md", p: 3, borderWidth: "1px", borderColor: "gray.200", children: /* @__PURE__ */ jsx10(Text5, { fontSize: "sm", fontWeight: "medium", children: formatCell(row[title], "string") }) }, row._id ?? idx)) })
823
- ] }, stageId);
824
- }) })
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
825
1757
  ] });
826
1758
  }
827
1759
 
828
1760
  // src/components/CoreDashboard.tsx
829
- import { Box as Box6, Heading as Heading5, SimpleGrid, Stack as Stack4, Text as Text6 } from "@chakra-ui/react";
1761
+ import { Box as Box7, Heading as Heading5, SimpleGrid as SimpleGrid2, Stack as Stack5, Text as Text8 } from "@chakra-ui/react";
830
1762
  import { useQueries } from "@tanstack/react-query";
831
- import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
1763
+ import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
832
1764
  function CoreDashboard({ label = "Dashboard", widgets = [] }) {
833
1765
  const { resource } = useOonApi();
834
1766
  const queries = useQueries({
@@ -845,17 +1777,17 @@ function CoreDashboard({ label = "Dashboard", widgets = [] }) {
845
1777
  }))
846
1778
  });
847
1779
  let queryIdx = 0;
848
- return /* @__PURE__ */ jsxs7(Stack4, { gap: 6, children: [
849
- /* @__PURE__ */ jsx11(Heading5, { size: "lg", children: label }),
850
- /* @__PURE__ */ jsx11(SimpleGrid, { columns: { base: 1, md: 2, lg: 3 }, gap: 4, children: widgets.map((w) => {
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) => {
851
1783
  if (w.kind === "custom" && w.render) {
852
- return /* @__PURE__ */ jsx11(Box6, { borderWidth: "1px", borderColor: "gray.200", borderRadius: "md", bg: "white", p: 5, children: w.render() }, w.id);
1784
+ return /* @__PURE__ */ jsx13(Box7, { borderWidth: "1px", borderColor: "gray.200", borderRadius: "md", bg: "white", p: 5, children: w.render() }, w.id);
853
1785
  }
854
1786
  const q = queries[queryIdx++];
855
1787
  const value = q?.isLoading ? "\u2026" : q?.isError ? "\u2014" : formatWidget(q?.data, w.kind, w.field);
856
- return /* @__PURE__ */ jsxs7(Box6, { borderWidth: "1px", borderColor: "gray.200", borderRadius: "md", bg: "white", p: 5, children: [
857
- /* @__PURE__ */ jsx11(Text6, { fontSize: "sm", color: "gray.500", children: w.label }),
858
- /* @__PURE__ */ jsx11(Heading5, { size: "xl", mt: 1, children: value })
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 })
859
1791
  ] }, w.id);
860
1792
  }) })
861
1793
  ] });
@@ -869,7 +1801,7 @@ function formatWidget(value, kind, field) {
869
1801
  }
870
1802
 
871
1803
  // src/app/moduleRegistry.tsx
872
- import { jsx as jsx12 } from "react/jsx-runtime";
1804
+ import { jsx as jsx14 } from "react/jsx-runtime";
873
1805
  function slug(value) {
874
1806
  return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
875
1807
  }
@@ -892,7 +1824,7 @@ function renderView(view) {
892
1824
  switch (view.type) {
893
1825
  case "collection": {
894
1826
  const v = view;
895
- return /* @__PURE__ */ jsx12(
1827
+ return /* @__PURE__ */ jsx14(
896
1828
  CoreCollection,
897
1829
  {
898
1830
  model: v.model,
@@ -906,11 +1838,11 @@ function renderView(view) {
906
1838
  }
907
1839
  case "document": {
908
1840
  const v = view;
909
- return /* @__PURE__ */ jsx12(CoreDocument, { model: v.model, label: viewLabel(v), approval: v.approval, attachments: v.attachments });
1841
+ return /* @__PURE__ */ jsx14(CoreDocument, { model: v.model, label: viewLabel(v), approval: v.approval, attachments: v.attachments });
910
1842
  }
911
1843
  case "pipeline": {
912
1844
  const v = view;
913
- return /* @__PURE__ */ jsx12(
1845
+ return /* @__PURE__ */ jsx14(
914
1846
  CorePipeline,
915
1847
  {
916
1848
  model: v.model,
@@ -922,7 +1854,7 @@ function renderView(view) {
922
1854
  }
923
1855
  case "dashboard": {
924
1856
  const v = view;
925
- return /* @__PURE__ */ jsx12(CoreDashboard, { label: viewLabel(v), widgets: v.widgets });
1857
+ return /* @__PURE__ */ jsx14(CoreDashboard, { label: viewLabel(v), widgets: v.widgets });
926
1858
  }
927
1859
  default:
928
1860
  return null;
@@ -970,20 +1902,23 @@ function resolveRegistry(config) {
970
1902
  }
971
1903
 
972
1904
  // src/app/createOonRouter.tsx
973
- import { createBrowserRouter } from "react-router-dom";
1905
+ import {
1906
+ Navigate as Navigate4,
1907
+ createBrowserRouter
1908
+ } from "react-router-dom";
974
1909
 
975
1910
  // src/shell/AuthenticatedLayout.tsx
976
- import { Box as Box8, Button as Button3, Flex as Flex6, Spacer as Spacer2, Text as Text8 } from "@chakra-ui/react";
1911
+ import { Box as Box9, Button as Button6, Flex as Flex7, Spacer as Spacer2, Text as Text10 } from "@chakra-ui/react";
977
1912
  import { Outlet } from "react-router-dom";
978
1913
 
979
1914
  // src/shell/Sidebar.tsx
980
- import { Box as Box7, Flex as Flex5, Image, Stack as Stack5, Text as Text7 } from "@chakra-ui/react";
1915
+ import { Box as Box8, Flex as Flex6, Image, Stack as Stack6, Text as Text9 } from "@chakra-ui/react";
981
1916
  import { NavLink } from "react-router-dom";
982
- import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
1917
+ import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
983
1918
  function groupBySection(items) {
984
1919
  const groups = /* @__PURE__ */ new Map();
985
1920
  for (const item of items) {
986
- const section = item.section ?? "";
1921
+ const section = item.section ?? "Geral";
987
1922
  if (!groups.has(section)) groups.set(section, []);
988
1923
  groups.get(section).push(item);
989
1924
  }
@@ -992,85 +1927,762 @@ function groupBySection(items) {
992
1927
  function Sidebar({ app, menu }) {
993
1928
  const { hasPermission } = useOonAuth();
994
1929
  const visible = menu.filter((item) => hasPermission(item.permissions));
995
- return /* @__PURE__ */ jsxs8(Box7, { as: "nav", w: "260px", minH: "100vh", borderRightWidth: "1px", borderColor: "gray.200", px: 4, py: 6, bg: "white", children: [
996
- /* @__PURE__ */ jsxs8(Flex5, { align: "center", gap: 3, mb: 8, px: 2, children: [
997
- app.logo ? /* @__PURE__ */ jsx13(Image, { src: app.logo, alt: app.name, maxH: "36px" }) : null,
998
- /* @__PURE__ */ jsx13(Text7, { fontWeight: "bold", fontSize: "md", color: "brand.500", children: app.name })
999
- ] }),
1000
- /* @__PURE__ */ jsx13(Stack5, { gap: 6, children: groupBySection(visible).map(([section, items]) => /* @__PURE__ */ jsxs8(Stack5, { gap: 1, children: [
1001
- section ? /* @__PURE__ */ jsx13(Text7, { fontSize: "xs", fontWeight: "semibold", color: "gray.400", textTransform: "uppercase", px: 3, mb: 1, children: section }) : null,
1002
- items.map((item) => /* @__PURE__ */ jsx13(NavLink, { to: item.href, end: item.href === "/", children: ({ isActive }) => /* @__PURE__ */ jsxs8(
1003
- Flex5,
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,
1004
2021
  {
2022
+ as: "header",
1005
2023
  align: "center",
1006
- gap: 2,
1007
- px: 3,
1008
- py: 2,
1009
- borderRadius: "md",
1010
- fontSize: "sm",
1011
- fontWeight: isActive ? "semibold" : "normal",
1012
- bg: isActive ? "brand.50" : "transparent",
1013
- color: isActive ? "brand.700" : "gray.700",
1014
- _hover: { bg: isActive ? "brand.50" : "gray.50" },
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,
1015
2034
  children: [
1016
- item.icon,
1017
- /* @__PURE__ */ jsx13("span", { children: item.label })
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
1018
2047
  ]
1019
2048
  }
1020
- ) }, item.href))
1021
- ] }, section || "_")) })
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
+ ] })
1022
2052
  ] });
1023
2053
  }
1024
2054
 
1025
- // src/shell/AuthenticatedLayout.tsx
1026
- import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
1027
- function AuthenticatedLayout({ app, menu }) {
1028
- const { user, logout } = useOonAuth();
1029
- return /* @__PURE__ */ jsxs9(Flex6, { align: "stretch", minH: "100vh", bg: "gray.50", children: [
1030
- /* @__PURE__ */ jsx14(Sidebar, { app, menu }),
1031
- /* @__PURE__ */ jsxs9(Flex6, { direction: "column", flex: "1", minW: 0, children: [
1032
- /* @__PURE__ */ jsxs9(Flex6, { as: "header", align: "center", px: 6, py: 3, borderBottomWidth: "1px", borderColor: "gray.200", bg: "white", children: [
1033
- /* @__PURE__ */ jsx14(Text8, { fontSize: "sm", color: "gray.500", children: app.title ?? app.name }),
1034
- /* @__PURE__ */ jsx14(Spacer2, {}),
1035
- user ? /* @__PURE__ */ jsxs9(Flex6, { align: "center", gap: 3, children: [
1036
- /* @__PURE__ */ jsx14(Text8, { fontSize: "sm", color: "gray.700", children: user.nome }),
1037
- /* @__PURE__ */ jsx14(Button3, { size: "xs", variant: "outline", colorPalette: "brand", onClick: logout, children: "Sair" })
1038
- ] }) : null
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 ? " *" : ""
1039
2398
  ] }),
1040
- /* @__PURE__ */ jsx14(Box8, { as: "main", flex: "1", p: 6, minW: 0, children: /* @__PURE__ */ jsx14(Outlet, {}) })
1041
- ] })
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 })
1042
2588
  ] });
1043
2589
  }
1044
2590
 
1045
2591
  // src/security/RouteGuard.tsx
1046
- import { Fragment, jsx as jsx15 } from "react/jsx-runtime";
2592
+ import { Fragment as Fragment3, jsx as jsx21 } from "react/jsx-runtime";
1047
2593
  function RouteGuard({
1048
2594
  permission,
1049
2595
  children
1050
2596
  }) {
1051
2597
  const { status, hasPermission } = useOonAuth();
1052
- if (status === "loading") return /* @__PURE__ */ jsx15(LoadingScreen, {});
1053
- if (status === "unauthenticated") return /* @__PURE__ */ jsx15(LoadingScreen, { message: "Redirecionando para o login..." });
2598
+ if (status === "loading") return /* @__PURE__ */ jsx21(LoadingScreen, {});
2599
+ if (status === "unauthenticated") return /* @__PURE__ */ jsx21(LoadingScreen, { message: "Redirecionando para o login..." });
1054
2600
  if (!hasPermission(permission)) {
1055
- return /* @__PURE__ */ jsx15(LoadingScreen, { message: "Voc\xEA n\xE3o tem acesso a esta tela." });
2601
+ return /* @__PURE__ */ jsx21(LoadingScreen, { message: "Voc\xEA n\xE3o tem acesso a esta tela." });
1056
2602
  }
1057
- return /* @__PURE__ */ jsx15(Fragment, { children });
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;
1058
2616
  }
1059
2617
 
1060
2618
  // src/app/createOonRouter.tsx
1061
- import { jsx as jsx16 } from "react/jsx-runtime";
2619
+ import { jsx as jsx23 } from "react/jsx-runtime";
1062
2620
  function createOonRouter({ app, menu, routes, security }) {
1063
2621
  const guardEnabled = security?.enableRouteGuard !== false;
1064
- const publicRoutes = routes.filter((r) => r.public).map((r) => ({ path: r.path, element: r.element }));
1065
- const privateRoutes = routes.filter((r) => !r.public).map((r) => {
1066
- const element = guardEnabled ? /* @__PURE__ */ jsx16(RouteGuard, { permission: r.permissions, children: r.element }) : r.element;
1067
- return r.path === "/" ? { index: true, element } : { path: r.path.replace(/^\//, ""), element };
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 };
1068
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
+ }
1069
2680
  const tree = [
2681
+ ...builtInPublicRoutes,
1070
2682
  ...publicRoutes,
1071
2683
  {
1072
2684
  path: "/",
1073
- element: /* @__PURE__ */ jsx16(AuthenticatedLayout, { app, menu }),
2685
+ element: /* @__PURE__ */ jsx23(ActivationGuard, { children: /* @__PURE__ */ jsx23(AppAccessGuard, { children: /* @__PURE__ */ jsx23(AuthenticatedLayout, { app, menu }) }) }),
1074
2686
  children: privateRoutes
1075
2687
  }
1076
2688
  ];
@@ -1078,29 +2690,44 @@ function createOonRouter({ app, menu, routes, security }) {
1078
2690
  }
1079
2691
 
1080
2692
  // src/app/OonApp.tsx
1081
- import { jsx as jsx17 } from "react/jsx-runtime";
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
+ }
1082
2698
  function OonApp({ config }) {
1083
- const { http, storage, router } = useMemo3(() => {
2699
+ const { http, storage, router } = useMemo4(() => {
1084
2700
  const storage2 = createTokenStorage(config.app.id);
1085
- const loginUrl = config.auth?.loginUrl ?? config.api.meusAppsUrl;
2701
+ const externalLoginUrl = config.auth?.loginUrl ?? config.api.meusAppsUrl;
1086
2702
  const http2 = createRestClient({
1087
2703
  api: config.api,
1088
2704
  appId: config.app.id,
1089
2705
  getToken: () => storage2.get(),
1090
2706
  onUnauthorized: () => {
1091
2707
  storage2.clear();
1092
- if (loginUrl) window.location.href = loginUrl;
2708
+ if (externalLoginUrl) {
2709
+ window.location.href = externalLoginUrl;
2710
+ return;
2711
+ }
2712
+ if (window.location.pathname !== "/login") {
2713
+ window.location.href = localLoginUrl();
2714
+ }
1093
2715
  }
1094
2716
  });
1095
2717
  const { routes, menu } = resolveRegistry(config);
1096
- const router2 = createOonRouter({ app: config.app, menu, routes, security: config.security });
2718
+ const router2 = createOonRouter({
2719
+ app: config.app,
2720
+ menu,
2721
+ routes,
2722
+ security: config.security
2723
+ });
1097
2724
  return { http: http2, storage: storage2, router: router2 };
1098
2725
  }, [config]);
1099
- return /* @__PURE__ */ jsx17(OonProviders, { config, http, storage, children: /* @__PURE__ */ jsx17(RouterProvider, { router }) });
2726
+ return /* @__PURE__ */ jsx24(OonProviders, { config, http, storage, children: /* @__PURE__ */ jsx24(RouterProvider, { router }) });
1100
2727
  }
1101
2728
 
1102
2729
  // src/start.tsx
1103
- import { jsx as jsx18 } from "react/jsx-runtime";
2730
+ import { jsx as jsx25 } from "react/jsx-runtime";
1104
2731
  function validateConfig(config) {
1105
2732
  if (!config?.app?.id) throw new Error("oonCoreFront.start: config.app.id \xE9 obrigat\xF3rio.");
1106
2733
  if (!config?.api?.baseUrl) throw new Error("oonCoreFront.start: config.api.baseUrl \xE9 obrigat\xF3rio.");
@@ -1134,7 +2761,7 @@ function start(config) {
1134
2761
  const el = document.getElementById(rootId);
1135
2762
  if (!el) throw new Error(`oonCoreFront.start: elemento #${rootId} n\xE3o encontrado no DOM.`);
1136
2763
  createRoot(el).render(
1137
- /* @__PURE__ */ jsx18(StrictMode, { children: /* @__PURE__ */ jsx18(OonApp, { config }) })
2764
+ /* @__PURE__ */ jsx25(StrictMode, { children: /* @__PURE__ */ jsx25(OonApp, { config }) })
1138
2765
  );
1139
2766
  }
1140
2767
  var oonCoreFront = { start };
@@ -1190,7 +2817,7 @@ function manifestToConfig(manifest, runtime) {
1190
2817
  assistantBaseUrl: runtime.assistantBaseUrl,
1191
2818
  versionPrefix: runtime.versionPrefix ?? ""
1192
2819
  },
1193
- auth: { mode: "bearer", tokenParam: "code" },
2820
+ auth: { mode: "bearer", tokenParam: "code", devToken: runtime.devToken },
1194
2821
  security: {
1195
2822
  enableRouteGuard: true,
1196
2823
  enablePermissionGate: true,
@@ -1222,34 +2849,34 @@ function defineOonModule(mod) {
1222
2849
  }
1223
2850
 
1224
2851
  // src/components/CoreIntegration.tsx
1225
- import { Badge, Box as Box9, Button as Button4, Flex as Flex7, Heading as Heading6, Stack as Stack6, Text as Text9 } from "@chakra-ui/react";
1226
- import { useMutation as useMutation2 } from "@tanstack/react-query";
1227
- import { jsx as jsx19, jsxs as jsxs10 } from "react/jsx-runtime";
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";
1228
2855
  function CoreIntegration({ label = "Integra\xE7\xF5es" }) {
1229
2856
  const { http } = useOonApi();
1230
2857
  const metadata = useCoreMetadata();
1231
- const reprocess = useMutation2({
2858
+ const reprocess = useMutation3({
1232
2859
  mutationFn: () => http.post("/integrations/actions/process-active", {}, { headers: { "x-oon-origin": "integration" } })
1233
2860
  });
1234
- if (metadata.isLoading) return /* @__PURE__ */ jsx19(LoadingScreen, {});
1235
- if (metadata.isError) return /* @__PURE__ */ jsx19(ErrorState, { error: metadata.error });
2861
+ if (metadata.isLoading) return /* @__PURE__ */ jsx26(LoadingScreen, {});
2862
+ if (metadata.isError) return /* @__PURE__ */ jsx26(ErrorState, { error: metadata.error });
1236
2863
  const integrations = metadata.data?.integrations ?? [];
1237
- return /* @__PURE__ */ jsxs10(Stack6, { gap: 6, children: [
1238
- /* @__PURE__ */ jsxs10(Flex7, { align: "center", justify: "space-between", children: [
1239
- /* @__PURE__ */ jsx19(Heading6, { size: "lg", children: label }),
1240
- /* @__PURE__ */ jsx19(Button4, { size: "sm", variant: "outline", loading: reprocess.isPending, onClick: () => reprocess.mutate(), children: "Reprocessar ativos" })
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" })
1241
2868
  ] }),
1242
- /* @__PURE__ */ jsx19(Stack6, { gap: 3, children: integrations.length === 0 ? /* @__PURE__ */ jsx19(Text9, { color: "gray.400", children: "Nenhuma integra\xE7\xE3o declarada." }) : integrations.map((it, idx) => /* @__PURE__ */ jsx19(
1243
- Box9,
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,
1244
2871
  {
1245
2872
  borderWidth: "1px",
1246
2873
  borderColor: "gray.200",
1247
2874
  borderRadius: "md",
1248
2875
  bg: "white",
1249
2876
  p: 4,
1250
- children: /* @__PURE__ */ jsxs10(Flex7, { align: "center", justify: "space-between", children: [
1251
- /* @__PURE__ */ jsx19(Text9, { fontWeight: "medium", children: it.label ?? it.name ?? "Integra\xE7\xE3o" }),
1252
- /* @__PURE__ */ jsx19(Badge, { colorPalette: it.status === "erro" ? "red" : it.status === "ativo" ? "green" : "gray", children: it.status ?? "\u2014" })
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" })
1253
2880
  ] })
1254
2881
  },
1255
2882
  it.id ?? it.name ?? idx
@@ -1258,36 +2885,36 @@ function CoreIntegration({ label = "Integra\xE7\xF5es" }) {
1258
2885
  }
1259
2886
 
1260
2887
  // src/components/CoreCurrency.tsx
1261
- import { Button as Button5, Flex as Flex8, Heading as Heading7, Stack as Stack7, Text as Text10 } from "@chakra-ui/react";
1262
- import { useMutation as useMutation3, useQueryClient as useQueryClient2 } from "@tanstack/react-query";
1263
- import { jsx as jsx20, jsxs as jsxs11 } from "react/jsx-runtime";
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";
1264
2891
  function CoreCurrency({ model = "Moeda", label = "Multi-moedas", endpoint = "/moedas", columns }) {
1265
2892
  const { http } = useOonApi();
1266
- const queryClient = useQueryClient2();
1267
- const updateRates = useMutation3({
2893
+ const queryClient = useQueryClient3();
2894
+ const updateRates = useMutation4({
1268
2895
  mutationFn: () => http.post(`${endpoint}/actions/update-rates`, {}, { headers: { "x-oon-origin": "integration" } }),
1269
2896
  onSuccess: () => queryClient.invalidateQueries({ queryKey: ["oon", "list", endpoint] })
1270
2897
  });
1271
- return /* @__PURE__ */ jsxs11(Stack7, { gap: 6, children: [
1272
- /* @__PURE__ */ jsxs11(Flex8, { align: "center", justify: "space-between", children: [
1273
- /* @__PURE__ */ jsx20(Heading7, { size: "lg", children: label }),
1274
- /* @__PURE__ */ jsx20(Button5, { size: "sm", colorPalette: "blue", loading: updateRates.isPending, onClick: () => updateRates.mutate(), children: "Atualizar cota\xE7\xF5es" })
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" })
1275
2902
  ] }),
1276
- updateRates.isError ? /* @__PURE__ */ jsx20(Text10, { fontSize: "sm", color: "red.600", children: updateRates.error?.message }) : null,
1277
- /* @__PURE__ */ jsx20(CoreCollection, { model, label: "", mode: columns ? "full" : "dynamic", endpoint, columns })
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 })
1278
2905
  ] });
1279
2906
  }
1280
2907
 
1281
2908
  // src/components/CoreAssistant.tsx
1282
- import { useState as useState5 } from "react";
1283
- import { Box as Box10, Button as Button6, Flex as Flex9, Heading as Heading8, Input as Input3, Stack as Stack8, Text as Text11 } from "@chakra-ui/react";
1284
- import { useMutation as useMutation4 } from "@tanstack/react-query";
1285
- import { jsx as jsx21, jsxs as jsxs12 } from "react/jsx-runtime";
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";
1286
2913
  function CoreAssistant({ assistantCode, label = "Assistente", entity, endpoint = "/assistant-runs" }) {
1287
2914
  const { http } = useOonApi();
1288
- const [messages, setMessages] = useState5([]);
1289
- const [input, setInput] = useState5("");
1290
- const ask = useMutation4({
2915
+ const [messages, setMessages] = useState10([]);
2916
+ const [input, setInput] = useState10("");
2917
+ const ask = useMutation5({
1291
2918
  mutationFn: async (message) => {
1292
2919
  const { data } = await http.post(
1293
2920
  endpoint,
@@ -1311,11 +2938,11 @@ function CoreAssistant({ assistantCode, label = "Assistente", entity, endpoint =
1311
2938
  setInput("");
1312
2939
  ask.mutate(text);
1313
2940
  };
1314
- return /* @__PURE__ */ jsxs12(Stack8, { gap: 4, maxW: "560px", children: [
1315
- /* @__PURE__ */ jsx21(Heading8, { size: "md", children: label }),
1316
- /* @__PURE__ */ jsx21(Box10, { borderWidth: "1px", borderColor: "gray.200", borderRadius: "md", bg: "white", p: 4, minH: "240px", children: /* @__PURE__ */ jsxs12(Stack8, { gap: 3, children: [
1317
- messages.length === 0 ? /* @__PURE__ */ jsx21(Text11, { color: "gray.400", fontSize: "sm", children: "Fa\xE7a uma pergunta ao assistente." }) : messages.map((m, idx) => /* @__PURE__ */ jsx21(Flex9, { justify: m.role === "user" ? "flex-end" : "flex-start", children: /* @__PURE__ */ jsx21(
1318
- Box10,
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,
1319
2946
  {
1320
2947
  bg: m.role === "user" ? "brand.500" : "gray.100",
1321
2948
  color: m.role === "user" ? "white" : "gray.800",
@@ -1327,12 +2954,12 @@ function CoreAssistant({ assistantCode, label = "Assistente", entity, endpoint =
1327
2954
  children: m.content
1328
2955
  }
1329
2956
  ) }, idx)),
1330
- ask.isPending ? /* @__PURE__ */ jsx21(Text11, { color: "gray.400", fontSize: "sm", children: "Pensando\u2026" }) : null,
1331
- ask.isError ? /* @__PURE__ */ jsx21(Text11, { color: "red.600", fontSize: "sm", children: ask.error?.message }) : null
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
1332
2959
  ] }) }),
1333
- /* @__PURE__ */ jsxs12(Flex9, { gap: 2, children: [
1334
- /* @__PURE__ */ jsx21(
1335
- Input3,
2960
+ /* @__PURE__ */ jsxs16(Flex12, { gap: 2, children: [
2961
+ /* @__PURE__ */ jsx28(
2962
+ Input6,
1336
2963
  {
1337
2964
  size: "sm",
1338
2965
  value: input,
@@ -1343,20 +2970,20 @@ function CoreAssistant({ assistantCode, label = "Assistente", entity, endpoint =
1343
2970
  }
1344
2971
  }
1345
2972
  ),
1346
- /* @__PURE__ */ jsx21(Button6, { size: "sm", colorPalette: "blue", onClick: send, loading: ask.isPending, children: "Enviar" })
2973
+ /* @__PURE__ */ jsx28(Button12, { size: "sm", colorPalette: "blue", onClick: send, loading: ask.isPending, children: "Enviar" })
1347
2974
  ] })
1348
2975
  ] });
1349
2976
  }
1350
2977
 
1351
2978
  // src/security/PermissionGate.tsx
1352
- import { Fragment as Fragment2, jsx as jsx22 } from "react/jsx-runtime";
2979
+ import { Fragment as Fragment4, jsx as jsx29 } from "react/jsx-runtime";
1353
2980
  function PermissionGate({
1354
2981
  permission,
1355
2982
  fallback = null,
1356
2983
  children
1357
2984
  }) {
1358
2985
  const { hasPermission } = useOonAuth();
1359
- return /* @__PURE__ */ jsx22(Fragment2, { children: hasPermission(permission) ? children : fallback });
2986
+ return /* @__PURE__ */ jsx29(Fragment4, { children: hasPermission(permission) ? children : fallback });
1360
2987
  }
1361
2988
  export {
1362
2989
  CoreAssistant,