@oondemand/oon-core-front 0.1.4 → 0.3.0

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