@sincpro/mobile 0.1.1 → 0.2.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.
Files changed (48) hide show
  1. package/dist/entrypoints/queue/printImage.subscriber.js +3 -3
  2. package/dist/entrypoints/ui/AppShell.d.ts +7 -0
  3. package/dist/entrypoints/ui/AppShell.js +25 -9
  4. package/dist/entrypoints/ui/common_provider.d.ts +6 -1
  5. package/dist/entrypoints/ui/common_provider.js +45 -1
  6. package/dist/entrypoints/ui/domain_switcher.js +5 -4
  7. package/dist/index.d.ts +0 -1
  8. package/dist/index.js +0 -1
  9. package/dist/infrastructure/ui/ToastHost.d.ts +6 -1
  10. package/dist/infrastructure/ui/ToastHost.js +18 -9
  11. package/dist/services/printer.service.d.ts +6 -0
  12. package/dist/services/printer.service.js +36 -3
  13. package/dist/ui/components/atoms/DebugBanner.js +4 -8
  14. package/dist/ui/components/atoms/PrinterIcon.d.ts +4 -0
  15. package/dist/ui/components/atoms/PrinterIcon.js +4 -0
  16. package/dist/ui/components/atoms/index.d.ts +1 -0
  17. package/dist/ui/components/atoms/index.js +1 -0
  18. package/dist/ui/components/molecules/BluetoothDeviceSelectorModal.js +16 -10
  19. package/dist/ui/components/molecules/DeadLetterErrorBlock.js +3 -2
  20. package/dist/ui/components/molecules/EventTimelineItem.js +31 -33
  21. package/dist/ui/components/molecules/GeoPermissionCard.d.ts +8 -0
  22. package/dist/ui/components/molecules/GeoPermissionCard.js +21 -0
  23. package/dist/ui/components/molecules/ProcessToast.d.ts +7 -4
  24. package/dist/ui/components/molecules/ProcessToast.js +45 -44
  25. package/dist/ui/components/molecules/ProcessToastProvider.js +1 -1
  26. package/dist/ui/components/molecules/TimeZoneSelector.d.ts +7 -0
  27. package/dist/ui/components/molecules/TimeZoneSelector.js +19 -0
  28. package/dist/ui/components/molecules/TimeZoneSelectorModal.d.ts +9 -0
  29. package/dist/ui/components/molecules/TimeZoneSelectorModal.js +61 -0
  30. package/dist/ui/components/molecules/index.d.ts +4 -0
  31. package/dist/ui/components/molecules/index.js +3 -0
  32. package/dist/ui/components/molecules/timezone.d.ts +4 -0
  33. package/dist/ui/components/molecules/timezone.js +1 -0
  34. package/dist/ui/components/organisms/BluetoothPrinterSelector.d.ts +3 -1
  35. package/dist/ui/components/organisms/BluetoothPrinterSelector.js +29 -9
  36. package/dist/ui/components/organisms/DatabaseInfoRow.js +1 -1
  37. package/dist/ui/components/organisms/DeadLetterQueueRow.js +3 -3
  38. package/dist/ui/components/organisms/EventRow.js +15 -15
  39. package/dist/ui/components/organisms/InjectableWebView.js +20 -10
  40. package/dist/ui/components/organisms/TableInfoInfoRow.js +1 -1
  41. package/dist/ui/screens/database/database.json_detail.js +3 -3
  42. package/dist/ui/screens/database/database.list.js +4 -5
  43. package/dist/ui/screens/database/database.table_rows.js +1 -2
  44. package/dist/ui/screens/dead_letter_queue/dead_letter_queue.list.js +4 -5
  45. package/dist/ui/screens/events/events.list.js +4 -5
  46. package/package.json +9 -5
  47. package/dist/ui/layouts/router_layouts.d.ts +0 -17
  48. package/dist/ui/layouts/router_layouts.js +0 -20
@@ -1,51 +1,52 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
1
  import { useCommon } from "../../../entrypoints/ui/common_provider";
3
- import { Display } from "@sincpro/mobile-ui/Display";
4
- import { Feedback } from "@sincpro/mobile-ui/Feedback";
5
- import { theme } from "@sincpro/mobile-ui/theme";
6
- import { cn } from "@sincpro/mobile-ui/theme/tw";
7
- import { Typography } from "@sincpro/mobile-ui/Typography";
8
- import { useEffect, useState } from "react";
9
- import { Animated, Platform, TouchableOpacity, View } from "react-native";
10
- const Icon = Display.Icon;
11
- const Spinner = Feedback.Spinner;
12
- const Text = Typography.Text;
13
- function ProcessToast({ position = "top" }) {
14
- const { currentActivity, lastError, isProcessing, dismiss } = useCommon();
15
- const [slideAnim] = useState(new Animated.Value(-100));
16
- const [showError, setShowError] = useState(false);
2
+ import { useToast } from "@sincpro/mobile-ui/Feedback";
3
+ import { useEffect, useRef } from "react";
4
+ const MAX_ERROR_LEN = 150;
5
+ /**
6
+ * Bridges the framework's queue/cron activity state (from CommonProvider) to the
7
+ * design-system Toast: a persistent `loading` toast while a queue/cron task runs, and a
8
+ * `danger` toast on failure. Renders nothing — the toast UI is owned by the DS
9
+ * `ToastProvider` (mounted in AppShell), so this must live inside it.
10
+ */
11
+ function ProcessToast() {
12
+ const { currentActivity, lastError, isProcessing } = useCommon();
13
+ const toast = useToast();
14
+ const loadingIdRef = useRef(null);
15
+ const shownErrorRef = useRef(null);
17
16
  useEffect(() => {
18
- if (lastError)
19
- setShowError(true);
20
- }, [lastError]);
17
+ const showLoading = isProcessing && !lastError;
18
+ if (showLoading) {
19
+ const label = currentActivity?.label ?? "Sincronizando...";
20
+ if (loadingIdRef.current) {
21
+ toast.update(loadingIdRef.current, { message: label, variant: "loading" });
22
+ }
23
+ else {
24
+ loadingIdRef.current = toast.loading(label);
25
+ }
26
+ }
27
+ else if (loadingIdRef.current) {
28
+ toast.hide(loadingIdRef.current);
29
+ loadingIdRef.current = null;
30
+ }
31
+ }, [isProcessing, lastError, currentActivity, toast]);
21
32
  useEffect(() => {
22
- Animated.spring(slideAnim, {
23
- toValue: isProcessing ? 0 : position === "top" ? -100 : 100,
24
- tension: 300,
25
- friction: 25,
26
- useNativeDriver: true,
27
- }).start();
28
- }, [isProcessing, slideAnim, position]);
29
- function renderToast() {
30
- if (!isProcessing) {
31
- return null;
33
+ if (!lastError) {
34
+ shownErrorRef.current = null;
35
+ return;
32
36
  }
33
- const hasError = !!lastError;
34
- const activity = lastError || currentActivity;
35
- const label = activity?.label || "Sincronizando...";
36
- function handleDismiss() {
37
- setShowError(false);
38
- dismiss();
37
+ const key = `${lastError.id}:${lastError.error ?? ""}`;
38
+ if (shownErrorRef.current === key) {
39
+ return;
39
40
  }
40
- return (_jsx(Animated.View, { className: "absolute left-4 right-4 z-50 self-center", style: [
41
- position === "top"
42
- ? { top: Platform.OS === "ios" ? 100 : 60 }
43
- : { bottom: Platform.OS === "ios" ? 100 : 80 },
44
- { transform: [{ translateY: slideAnim }] },
45
- ], children: _jsxs(View, { className: cn("bg-white/[0.98] rounded-xl px-3.5 py-3 border-l-[3px] border-emerald-500 shadow-md", hasError && "bg-red-50/[0.98] border-red-500"), children: [_jsxs(View, { className: "flex-row items-center gap-3", children: [_jsx(View, { className: "w-5 h-5 justify-center items-center", children: hasError ? (_jsx(Icon, { color: theme.danger, name: "alert-circle", size: 16 })) : (_jsx(Spinner, { size: "small" })) }), _jsx(View, { className: "flex-1", children: _jsx(Text, { className: "text-gray-900 text-[13px] font-medium leading-4", variant: "captionSmall", children: hasError ? `Error: ${label}` : label }) }), _jsx(TouchableOpacity, { accessibilityLabel: "Cerrar notificación", className: "w-6 h-6 justify-center items-center rounded-full bg-gray-100", onPress: handleDismiss, children: _jsx(Icon, { color: theme.text.secondary, name: "close", size: 14 }) })] }), showError && lastError?.error && (_jsx(View, { className: "mt-2 pt-2 border-t border-gray-100", children: _jsx(Text, { className: "text-red-500 text-[11px] leading-[15px] font-mono", variant: "captionSmall", children: lastError.error.length > 150
46
- ? `${lastError.error.substring(0, 150)}...`
47
- : lastError.error }) }))] }) }));
48
- }
49
- return renderToast();
41
+ shownErrorRef.current = key;
42
+ const raw = lastError.error;
43
+ const detail = raw
44
+ ? raw.length > MAX_ERROR_LEN
45
+ ? `${raw.slice(0, MAX_ERROR_LEN)}...`
46
+ : raw
47
+ : undefined;
48
+ toast.danger(detail ?? lastError.label, { title: `Error: ${lastError.label}` });
49
+ }, [lastError, toast]);
50
+ return null;
50
51
  }
51
52
  export default ProcessToast;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
2
  import ProcessToast from "./ProcessToast";
3
3
  export function ProcessToastProvider({ children }) {
4
- return (_jsxs(_Fragment, { children: [children, _jsx(ProcessToast, { position: "top" })] }));
4
+ return (_jsxs(_Fragment, { children: [children, _jsx(ProcessToast, {})] }));
5
5
  }
@@ -0,0 +1,7 @@
1
+ import { TimezoneLocale } from "./timezone";
2
+ interface TimeZoneSelectorProps {
3
+ onSelect?: (tz: TimezoneLocale) => void;
4
+ timezone?: string | null;
5
+ }
6
+ export declare function TimeZoneSelector({ onSelect, timezone }: TimeZoneSelectorProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "nativewind/jsx-runtime";
2
+ import { Display, Form } from "@sincpro/mobile-ui/index";
3
+ import { theme } from "@sincpro/mobile-ui/theme/useTheme";
4
+ import { Typography } from "@sincpro/mobile-ui/Typography";
5
+ import { useState } from "react";
6
+ import { View } from "react-native";
7
+ import { TimeZoneSelectorModal } from "./TimeZoneSelectorModal";
8
+ export function TimeZoneSelector({ onSelect, timezone }) {
9
+ const [currentTimezone, setCurrentTimezone] = useState(timezone);
10
+ const [selectorVisible, setSelectorVisible] = useState(false);
11
+ function handleTimezoneSelected(tz) {
12
+ setCurrentTimezone(tz.timezone);
13
+ onSelect?.(tz);
14
+ }
15
+ const displayTimezone = currentTimezone
16
+ ? currentTimezone.replace(/_/g, " ").replace("America/", "")
17
+ : "No configurada";
18
+ return (_jsxs(Display.Card, { className: "my-2.5", children: [_jsxs(View, { className: "flex-row items-start", children: [_jsx(Display.Icon, { color: currentTimezone ? theme.info : theme.text.tertiary, name: "schedule", size: 35, type: "material" }), _jsxs(View, { className: "ml-2.5 flex-1", children: [_jsx(Typography.Text, { className: "text-base mb-1", semibold: true, children: "Zona Horaria" }), currentTimezone ? (_jsxs(View, { className: "mt-2 bg-bg-card rounded-lg p-3", children: [_jsx(Typography.Text, { className: "text-text-primary", semibold: true, children: displayTimezone }), _jsx(Typography.Text, { className: "text-text-tertiary text-xs", children: currentTimezone }), _jsx(View, { className: "flex-row gap-2 mt-2", children: _jsx(Form.Button, { onPress: () => setSelectorVisible(true), size: "small", title: "Cambiar", variant: "outline" }) })] })) : (_jsxs(_Fragment, { children: [_jsx(Typography.Text, { className: "text-text-secondary mb-2", children: "Selecciona una zona horaria" }), _jsx(Form.Button, { onPress: () => setSelectorVisible(true), size: "small", title: "Seleccionar Zona Horaria", variant: "cta" })] }))] })] }), _jsx(TimeZoneSelectorModal, { currentTimezone: currentTimezone, onClose: () => setSelectorVisible(false), onSelect: handleTimezoneSelected, visible: selectorVisible })] }));
19
+ }
@@ -0,0 +1,9 @@
1
+ import { TimezoneLocale } from "./timezone";
2
+ interface TimeZoneSelectorModalProps {
3
+ visible: boolean;
4
+ onClose: () => void;
5
+ onSelect: (tz: TimezoneLocale) => void;
6
+ currentTimezone?: string | null;
7
+ }
8
+ declare function TimeZoneSelectorModal({ visible, onClose, onSelect, currentTimezone, }: TimeZoneSelectorModalProps): import("react/jsx-runtime").JSX.Element;
9
+ export { TimeZoneSelectorModal };
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
+ import Sheet from "@sincpro/mobile-ui/Dialog/Sheet";
3
+ import { Display } from "@sincpro/mobile-ui/Display";
4
+ import { Form } from "@sincpro/mobile-ui/Form";
5
+ import { useTheme } from "@sincpro/mobile-ui/theme";
6
+ import { Typography } from "@sincpro/mobile-ui/Typography";
7
+ import { useMemo, useState } from "react";
8
+ import { TextInput, TouchableOpacity, View } from "react-native";
9
+ const TIMEZONES_LOCALES = {
10
+ bolivia: { timezone: "America/La_Paz", locale: "es-BO" },
11
+ costaRica: { timezone: "America/Costa_Rica", locale: "es-CR" },
12
+ guatemala: { timezone: "America/Guatemala", locale: "es-GT" },
13
+ argentina: { timezone: "America/Argentina/Buenos_Aires", locale: "es-AR" },
14
+ peru: { timezone: "America/Lima", locale: "es-PE" },
15
+ newYork: { timezone: "America/New_York", locale: "en-US" },
16
+ japon: { timezone: "Asia/Tokyo", locale: "ja-JP" },
17
+ };
18
+ function TimezoneRow({ item, isSelected, onSelect, }) {
19
+ const theme = useTheme();
20
+ return (_jsxs(TouchableOpacity, { activeOpacity: 0.7, className: `flex-row items-center p-4 rounded-xl ${isSelected ? "bg-bg-accent" : "bg-bg-card"}`, onPress: onSelect, children: [_jsx(Display.Icon, { color: isSelected ? theme.accent : theme.text.secondary, name: "schedule", size: 24, type: "material" }), _jsxs(View, { className: "ml-3 flex-1", children: [_jsx(Typography.Text, { semibold: true, children: item.label }), _jsx(Typography.Text, { className: "text-text-tertiary text-xs", children: item.locale })] }), isSelected && (_jsx(View, { className: "w-6 h-6 rounded-full bg-accent items-center justify-center", children: _jsx(Display.Icon, { color: theme.text.inverse, name: "check", size: 16, type: "material" }) }))] }));
21
+ }
22
+ function EmptyState() {
23
+ const theme = useTheme();
24
+ return (_jsxs(View, { className: "p-8 items-center justify-center", style: { minHeight: 200 }, children: [_jsx(Display.Icon, { color: theme.text.tertiary, name: "search-off", size: 48, type: "material" }), _jsx(Typography.Text, { className: "text-text-secondary text-center mt-4", children: "No se encontraron zonas horarias" })] }));
25
+ }
26
+ function TimeZoneSelectorModal({ visible, onClose, onSelect, currentTimezone, }) {
27
+ const theme = useTheme();
28
+ const [query, setQuery] = useState("");
29
+ const [selectedTimezone, setSelectedTimezone] = useState(currentTimezone || null);
30
+ const timezoneList = useMemo(() => {
31
+ return Object.values(TIMEZONES_LOCALES).map((tz) => ({
32
+ ...tz,
33
+ label: tz.timezone.replace(/_/g, " ").replace("America/", ""),
34
+ }));
35
+ }, []);
36
+ const filteredTimezones = useMemo(() => {
37
+ if (!query.trim())
38
+ return timezoneList;
39
+ const normalizedQuery = query
40
+ .toLowerCase()
41
+ .normalize("NFD")
42
+ .replace(/[\u0300-\u036f]/g, "");
43
+ return timezoneList.filter(({ label, timezone }) => {
44
+ const normalizedLabel = label
45
+ .toLowerCase()
46
+ .normalize("NFD")
47
+ .replace(/[\u0300-\u036f]/g, "");
48
+ const normalizedTz = timezone.toLowerCase();
49
+ return (normalizedLabel.includes(normalizedQuery) || normalizedTz.includes(normalizedQuery));
50
+ });
51
+ }, [query, timezoneList]);
52
+ function handleConfirm() {
53
+ const selected = timezoneList.find((tz) => tz.timezone === selectedTimezone);
54
+ if (selected) {
55
+ onSelect({ timezone: selected.timezone, locale: selected.locale });
56
+ onClose();
57
+ }
58
+ }
59
+ return (_jsxs(Sheet, { onClose: onClose, title: "Zona Horaria", visible: visible, children: [_jsx(Typography.Text, { className: "text-text-secondary text-xs mb-3", children: "Selecciona tu zona horaria" }), _jsx(TextInput, { className: "bg-bg-muted px-4 py-3 rounded-xl mb-2", onChangeText: setQuery, placeholder: "Buscar zona horaria...", placeholderTextColor: theme.text.tertiary, value: query }), filteredTimezones.length === 0 ? (_jsx(EmptyState, {})) : (_jsx(View, { className: "gap-1", children: filteredTimezones.map((item) => (_jsx(TimezoneRow, { isSelected: selectedTimezone === item.timezone, item: item, onSelect: () => setSelectedTimezone(item.timezone) }, item.timezone))) })), _jsxs(View, { className: "flex-row gap-2 mt-4", children: [_jsx(View, { className: "flex-1", children: _jsx(Form.Button, { onPress: onClose, title: "Cancelar", variant: "outline" }) }), selectedTimezone ? (_jsx(View, { className: "flex-1", children: _jsx(Form.Button, { onPress: handleConfirm, title: "Confirmar", variant: "cta" }) })) : null] })] }));
60
+ }
61
+ export { TimeZoneSelectorModal };
@@ -1,5 +1,9 @@
1
1
  export { BluetoothDeviceSelectorModal } from "./BluetoothDeviceSelectorModal";
2
2
  export { default as DeadLetterErrorBlock } from "./DeadLetterErrorBlock";
3
3
  export { default as EventTimelineItem } from "./EventTimelineItem";
4
+ export { default as GeoPermissionCard } from "./GeoPermissionCard";
4
5
  export { default as ProcessToast } from "./ProcessToast";
5
6
  export { ProcessToastProvider } from "./ProcessToastProvider";
7
+ export type { TimezoneLocale } from "./timezone";
8
+ export { TimeZoneSelector } from "./TimeZoneSelector";
9
+ export { TimeZoneSelectorModal } from "./TimeZoneSelectorModal";
@@ -1,5 +1,8 @@
1
1
  export { BluetoothDeviceSelectorModal } from "./BluetoothDeviceSelectorModal";
2
2
  export { default as DeadLetterErrorBlock } from "./DeadLetterErrorBlock";
3
3
  export { default as EventTimelineItem } from "./EventTimelineItem";
4
+ export { default as GeoPermissionCard } from "./GeoPermissionCard";
4
5
  export { default as ProcessToast } from "./ProcessToast";
5
6
  export { ProcessToastProvider } from "./ProcessToastProvider";
7
+ export { TimeZoneSelector } from "./TimeZoneSelector";
8
+ export { TimeZoneSelectorModal } from "./TimeZoneSelectorModal";
@@ -0,0 +1,4 @@
1
+ export interface TimezoneLocale {
2
+ timezone: string;
3
+ locale: string;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -2,6 +2,8 @@ import { ISelectedPrinter } from "../../../domain/print";
2
2
  interface BluetoothPrinterSelectorProps {
3
3
  onPrinterSelected?: (printer: ISelectedPrinter) => void;
4
4
  onConnectionChange?: (isConnected: boolean) => void;
5
+ /** Render as section rows (no outer card) — use inside FormViewV2.Content.Section. */
6
+ compact?: boolean;
5
7
  }
6
- declare function BluetoothPrinterSelector({ onPrinterSelected, onConnectionChange, }: BluetoothPrinterSelectorProps): import("react/jsx-runtime").JSX.Element;
8
+ declare function BluetoothPrinterSelector({ onPrinterSelected, onConnectionChange, compact, }: BluetoothPrinterSelectorProps): import("react/jsx-runtime").JSX.Element;
7
9
  export { BluetoothPrinterSelector };
@@ -1,9 +1,11 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
2
  import { bluetoothService } from "../../../services/bluetooth.service";
3
3
  import { printerService } from "../../../services/printer.service";
4
+ import PrinterIcon from "../../../ui/components/atoms/PrinterIcon";
4
5
  import { BluetoothDeviceSelectorModal } from "../../../ui/components/molecules/BluetoothDeviceSelectorModal";
5
- import { Display, Form } from "@sincpro/mobile-ui";
6
- import PrinterIcon from "@sincpro/mobile-ui/icons/PrinterIcon";
6
+ import { Display } from "@sincpro/mobile-ui/Display";
7
+ import { Form } from "@sincpro/mobile-ui/Form";
8
+ import { useTheme } from "@sincpro/mobile-ui/theme";
7
9
  import { Typography } from "@sincpro/mobile-ui/Typography";
8
10
  import { useCallback, useEffect, useState } from "react";
9
11
  import { View } from "react-native";
@@ -11,21 +13,22 @@ import { tv } from "tailwind-variants";
11
13
  const statusTextVariants = tv({
12
14
  variants: {
13
15
  granted: {
14
- true: "text-green-600",
15
- false: "text-red-500",
16
+ true: "text-success",
17
+ false: "text-danger",
16
18
  },
17
19
  },
18
20
  });
19
21
  function PermissionRequired({ loading, onRequestPermission, }) {
20
- return (_jsxs(_Fragment, { children: [_jsx(Typography.Text, { className: statusTextVariants({ granted: false }), children: "Permiso de Bluetooth requerido" }), _jsx(Form.Button, { loading: loading, onPress: onRequestPermission, size: "small", title: "Solicitar Permiso" })] }));
22
+ return (_jsxs(_Fragment, { children: [_jsx(Typography.Text, { className: statusTextVariants({ granted: false }), children: "Permiso de Bluetooth requerido" }), _jsx(Form.Button, { loading: loading, onPress: onRequestPermission, size: "small", title: "Solicitar Permiso", variant: "cta" })] }));
21
23
  }
22
24
  function PrinterInfo({ printer, isConnected, loading, onConnect, onTestPrint, onChangePrinter, }) {
23
- return (_jsxs(View, { className: "mt-2 bg-white rounded-lg p-3", children: [_jsx(Typography.Text, { className: "text-gray-700", semibold: true, children: printer.name }), _jsx(Typography.Text, { className: "text-gray-400 text-xs", children: printer.address }), _jsxs(View, { className: "flex-row gap-2 mt-2", children: [!isConnected ? (_jsx(Form.Button, { loading: loading, onPress: onConnect, size: "small", title: "Conectar", variant: "primary" })) : (_jsx(Form.Button, { loading: loading, onPress: onTestPrint, size: "small", title: "Imprimir Prueba", variant: "secondary" })), _jsx(Form.Button, { onPress: onChangePrinter, size: "small", title: "Cambiar", variant: "outline" })] }), isConnected && (_jsxs(View, { className: "flex-row items-center mt-2", children: [_jsx(View, { className: "w-2 h-2 rounded-full bg-green-500 mr-2" }), _jsx(Typography.Text, { className: "text-green-600 text-xs", children: "Conectado" })] }))] }));
25
+ return (_jsxs(View, { className: "mt-2 bg-bg-muted rounded-lg p-3", children: [_jsx(Typography.Text, { className: "text-text-primary", semibold: true, children: printer.name }), _jsx(Typography.Text, { className: "text-text-tertiary text-xs", children: printer.address }), _jsxs(View, { className: "flex-row gap-2 mt-2", children: [!isConnected ? (_jsx(Form.Button, { loading: loading, onPress: onConnect, size: "small", title: "Conectar", variant: "cta" })) : (_jsx(Form.Button, { loading: loading, onPress: onTestPrint, size: "small", title: "Imprimir Prueba", variant: "secondary" })), _jsx(Form.Button, { onPress: onChangePrinter, size: "small", title: "Cambiar", variant: "outline" })] }), isConnected && (_jsxs(View, { className: "flex-row items-center mt-2", children: [_jsx(View, { className: "w-2 h-2 rounded-full bg-success mr-2" }), _jsx(Typography.Text, { className: "text-success text-xs", children: "Conectado" })] }))] }));
24
26
  }
25
27
  function NoPrinterSelected({ loading, onSelectPrinter, }) {
26
- return (_jsx(Form.Button, { loading: loading, onPress: onSelectPrinter, size: "small", title: "Seleccionar Impresora" }));
28
+ return (_jsx(Form.Button, { loading: loading, onPress: onSelectPrinter, size: "small", title: "Seleccionar Impresora", variant: "cta" }));
27
29
  }
28
- function BluetoothPrinterSelector({ onPrinterSelected, onConnectionChange, }) {
30
+ function BluetoothPrinterSelector({ onPrinterSelected, onConnectionChange, compact = false, }) {
31
+ const theme = useTheme();
29
32
  const [hasPermission, setHasPermission] = useState(null);
30
33
  const [selectedPrinter, setSelectedPrinter] = useState(null);
31
34
  const [isConnected, setIsConnected] = useState(false);
@@ -87,6 +90,23 @@ function BluetoothPrinterSelector({ onPrinterSelected, onConnectionChange, }) {
87
90
  onPrinterSelected?.(printer);
88
91
  }
89
92
  const granted = hasPermission === true;
90
- return (_jsxs(View, { className: "bg-slate-50 rounded-xl p-5 my-2.5 shadow-sm", children: [_jsxs(View, { className: "flex-row items-start", children: [_jsx(Display.Icon, { color: granted ? "#22c55e" : "#ef4444", customIcon: PrinterIcon, size: 35, type: "custom" }), _jsxs(View, { className: "ml-2.5 flex-1", children: [_jsx(Typography.Text, { className: "text-base mb-1", semibold: true, children: "Impresora Bluetooth" }), hasPermission === null ? (_jsx(Typography.Text, { className: "text-gray-500", children: "Verificando permisos..." })) : !granted ? (_jsx(PermissionRequired, { loading: loading, onRequestPermission: handleRequestPermission })) : (_jsxs(_Fragment, { children: [_jsx(Typography.Text, { className: statusTextVariants({ granted: true }), children: "Bluetooth habilitado" }), selectedPrinter ? (_jsx(PrinterInfo, { isConnected: isConnected, loading: loading, onChangePrinter: () => setSelectorVisible(true), onConnect: handleConnect, onTestPrint: handleTestPrint, printer: selectedPrinter })) : (_jsx(NoPrinterSelected, { loading: loading, onSelectPrinter: () => setSelectorVisible(true) }))] }))] })] }), _jsx(BluetoothDeviceSelectorModal, { onClose: () => setSelectorVisible(false), onSelect: handleDeviceSelected, visible: selectorVisible })] }));
93
+ const modal = (_jsx(BluetoothDeviceSelectorModal, { onClose: () => setSelectorVisible(false), onSelect: handleDeviceSelected, visible: selectorVisible }));
94
+ if (compact) {
95
+ if (hasPermission === null) {
96
+ return (_jsxs(_Fragment, { children: [_jsx(Display.MenuButton, { description: "Verificando permisos...", icon: PrinterIcon, label: "Impresora Bluetooth", showDivider: false }), modal] }));
97
+ }
98
+ if (!granted) {
99
+ return (_jsxs(_Fragment, { children: [_jsx(Display.MenuButton, { description: "Permiso de Bluetooth requerido", icon: PrinterIcon, label: "Impresora Bluetooth", onPress: handleRequestPermission, showDivider: false }), modal] }));
100
+ }
101
+ if (!selectedPrinter) {
102
+ return (_jsxs(_Fragment, { children: [_jsx(Display.MenuButton, { description: "No configurada", icon: PrinterIcon, label: "Impresora Bluetooth", onPress: () => setSelectorVisible(true), showDivider: false }), modal] }));
103
+ }
104
+ return (_jsxs(_Fragment, { children: [_jsx(Display.MenuButton, { description: loading
105
+ ? "Conectando..."
106
+ : isConnected
107
+ ? "Conectado"
108
+ : "Sin conexión — toca para conectar", icon: PrinterIcon, label: selectedPrinter.name, onPress: !isConnected && !loading ? handleConnect : undefined }), _jsx(Display.MenuButton, { description: "Seleccionar otro dispositivo", icon: PrinterIcon, label: "Cambiar impresora", onPress: () => setSelectorVisible(true), showDivider: false }), modal] }));
109
+ }
110
+ return (_jsxs(Display.Card, { className: "my-2.5", children: [_jsxs(View, { className: "flex-row items-start", children: [_jsx(Display.Icon, { color: granted ? theme.success : theme.danger, customIcon: PrinterIcon, size: 35, type: "custom" }), _jsxs(View, { className: "ml-2.5 flex-1", children: [_jsx(Typography.Text, { className: "text-base mb-1", semibold: true, children: "Impresora Bluetooth" }), hasPermission === null ? (_jsx(Typography.Text, { className: "text-text-secondary", children: "Verificando permisos..." })) : !granted ? (_jsx(PermissionRequired, { loading: loading, onRequestPermission: handleRequestPermission })) : (_jsxs(_Fragment, { children: [_jsx(Typography.Text, { className: statusTextVariants({ granted: true }), children: "Bluetooth habilitado" }), selectedPrinter ? (_jsx(PrinterInfo, { isConnected: isConnected, loading: loading, onChangePrinter: () => setSelectorVisible(true), onConnect: handleConnect, onTestPrint: handleTestPrint, printer: selectedPrinter })) : (_jsx(NoPrinterSelected, { loading: loading, onSelectPrinter: () => setSelectorVisible(true) }))] }))] })] }), _jsx(BluetoothDeviceSelectorModal, { onClose: () => setSelectorVisible(false), onSelect: handleDeviceSelected, visible: selectorVisible })] }));
91
111
  }
92
112
  export { BluetoothPrinterSelector };
@@ -3,6 +3,6 @@ import { Display } from "@sincpro/mobile-ui/Display";
3
3
  import { ListViewV2 } from "@sincpro/mobile-ui/views/ListViewV2";
4
4
  import { View } from "react-native";
5
5
  function DatabaseInfoRow({ item, onPress }) {
6
- return (_jsxs(ListViewV2.Content.Row, { onPress: onPress, children: [_jsx(ListViewV2.Content.Row.Avatar, { children: _jsx(View, { className: "justify-center items-center p-2 rounded-lg bg-gray-100", children: _jsx(Display.Icon, { name: "database", size: 32, type: "antdesign" }) }) }), _jsxs(ListViewV2.Content.Row.Content, { children: [_jsx(ListViewV2.Content.Row.Title, { numberOfLines: 2, children: item.name }), _jsx(ListViewV2.Content.Row.Subtitle, { children: item.description || "N/A" })] })] }));
6
+ return (_jsxs(ListViewV2.Content.Row, { onPress: onPress, children: [_jsx(ListViewV2.Content.Row.Avatar, { children: _jsx(View, { className: "justify-center items-center p-2 rounded-lg bg-bg-muted", children: _jsx(Display.Icon, { name: "database", size: 32, type: "antdesign" }) }) }), _jsxs(ListViewV2.Content.Row.Content, { children: [_jsx(ListViewV2.Content.Row.Title, { numberOfLines: 2, children: item.name }), _jsx(ListViewV2.Content.Row.Subtitle, { children: item.description || "N/A" })] })] }));
7
7
  }
8
8
  export default DatabaseInfoRow;
@@ -54,19 +54,19 @@ function DeadLetterQueueRow({ item, onRetry }) {
54
54
  return (_jsxs(View, { className: "flex-row items-start mb-2", children: [_jsxs(View, { className: "flex-1 pr-2", children: [_jsx(Typography.Text, { className: "mb-1", numberOfLines: 2, semibold: true, variant: "body", children: friendlyName }), _jsx(Display.CopyableText, { value: event.name })] }), _jsx(View, { className: "ml-2 self-start", children: renderBadges() })] }));
55
55
  }
56
56
  function renderMeta() {
57
- return (_jsxs(View, { className: "flex-row items-center justify-between mb-3", children: [_jsx(Display.Date, { className: "text-gray-800", showTime: true, textVariant: "bodySmall", value: event.createdAt }), _jsx(Display.CopyableText, { label: "Copiar UUID", value: event.uuid })] }));
57
+ return (_jsxs(View, { className: "flex-row items-center justify-between mb-3", children: [_jsx(Display.Date, { className: "text-text-primary", showTime: true, textVariant: "bodySmall", value: event.createdAt }), _jsx(Display.CopyableText, { label: "Copiar UUID", value: event.uuid })] }));
58
58
  }
59
59
  function renderPayload() {
60
60
  if (!showPayload) {
61
61
  return null;
62
62
  }
63
- return (_jsxs(View, { className: "bg-gray-50 rounded-lg p-2 border border-gray-200 mb-2.5", children: [_jsx(Typography.Text, { className: "text-gray-700 mb-1", semibold: true, variant: "bodySmall", children: "Payload" }), _jsx(JsonPreview, { selectedJson: event.asJSON(true) })] }));
63
+ return (_jsxs(View, { className: "bg-bg-muted rounded-lg p-2 border border-border-default mb-2.5", children: [_jsx(Typography.Text, { className: "text-text-primary mb-1", semibold: true, variant: "bodySmall", children: "Payload" }), _jsx(JsonPreview, { selectedJson: event.asJSON(true) })] }));
64
64
  }
65
65
  function renderActions() {
66
66
  return (_jsxs(View, { className: "mt-2 gap-2", children: [_jsx(Form.Button, { className: "w-full", onPress: () => setShowError((v) => !v), size: "small", title: showError ? "Ocultar error" : "Ver error", variant: "secondary" }), _jsx(Form.Button, { className: "w-full", onPress: togglePayload, size: "small", title: showPayload ? "Ocultar payload" : "Ver payload", variant: "secondary" }), _jsx(Form.Button, { className: "w-full", onPress: async () => {
67
67
  await Clipboard.setStringAsync(event.asJSON(true));
68
68
  }, size: "small", title: "Copiar payload", variant: "secondary" }), _jsx(Form.Button, { className: "w-full", disabled: sending, loading: sending, onPress: handleRetry, size: "medium", title: sending ? "Reintentando…" : "Reintentar", variant: "primary" })] }));
69
69
  }
70
- return (_jsxs(View, { className: "bg-white border border-gray-200 rounded-2xl mx-4 p-3.5 mb-3 shadow-sm", children: [renderHeader(), renderMeta(), _jsx(DeadLetterErrorBlock, { errorMessage: event.errorMessage, expanded: showError, onToggle: () => setShowError((v) => !v) }), renderPayload(), renderActions()] }));
70
+ return (_jsxs(View, { className: "bg-bg-card border border-border-default rounded-2xl mx-4 p-3.5 mb-3 shadow-sm", children: [renderHeader(), renderMeta(), _jsx(DeadLetterErrorBlock, { errorMessage: event.errorMessage, expanded: showError, onToggle: () => setShowError((v) => !v) }), renderPayload(), renderActions()] }));
71
71
  }
72
72
  export default DeadLetterQueueRow;
@@ -17,28 +17,28 @@ function EventRow({ item, onRetry }) {
17
17
  const statusBadge = (() => {
18
18
  if (item.status === EEventStatus.FAILED) {
19
19
  return {
20
- backgroundClass: "bg-red-100 border-red-200",
21
- textClass: "text-red-700",
20
+ backgroundClass: "bg-danger-light border-danger",
21
+ textClass: "text-danger",
22
22
  label: "FAILED",
23
23
  };
24
24
  }
25
25
  if (item.status === EEventStatus.ACKNOWLEDGED) {
26
26
  return {
27
- backgroundClass: "bg-green-100 border-green-200",
28
- textClass: "text-green-700",
27
+ backgroundClass: "bg-success-light border-success",
28
+ textClass: "text-success",
29
29
  label: "ACKNOWLEDGED",
30
30
  };
31
31
  }
32
32
  if (item.status === EEventStatus.PENDING || item.status === EEventStatus.PROCESSING) {
33
33
  return {
34
- backgroundClass: "bg-blue-100 border-blue-200",
35
- textClass: "text-blue-700",
34
+ backgroundClass: "bg-info-light border-info",
35
+ textClass: "text-info",
36
36
  label: item.status,
37
37
  };
38
38
  }
39
39
  return {
40
- backgroundClass: "bg-gray-100 border-gray-200",
41
- textClass: "text-gray-700",
40
+ backgroundClass: "bg-bg-muted border-border-default",
41
+ textClass: "text-text-primary",
42
42
  label: item.status,
43
43
  };
44
44
  })();
@@ -54,15 +54,15 @@ function EventRow({ item, onRetry }) {
54
54
  }
55
55
  };
56
56
  function renderMeta() {
57
- return (_jsxs(View, { className: "flex-row items-center justify-between mb-3", children: [_jsx(Display.Date, { className: "text-gray-800", showTime: true, textVariant: "bodySmall", value: item.createdAt }), _jsx(Display.CopyableText, { label: "Copiar UUID", value: item.uuid })] }));
57
+ return (_jsxs(View, { className: "flex-row items-center justify-between mb-3", children: [_jsx(Display.Date, { className: "text-text-primary", showTime: true, textVariant: "bodySmall", value: item.createdAt }), _jsx(Display.CopyableText, { label: "Copiar UUID", value: item.uuid })] }));
58
58
  }
59
59
  function renderBadges() {
60
60
  const badges = [];
61
61
  const statusVariantMap = {
62
- "bg-red-100 border-red-200": "danger",
63
- "bg-green-100 border-green-200": "successDark",
64
- "bg-blue-100 border-blue-200": "infoDark",
65
- "bg-gray-100 border-gray-200": "warning",
62
+ "bg-danger-light border-danger": "danger",
63
+ "bg-success-light border-success": "successDark",
64
+ "bg-info-light border-info": "infoDark",
65
+ "bg-bg-muted border-border-default": "warning",
66
66
  };
67
67
  const variant = statusVariantMap[statusBadge.backgroundClass] || "info";
68
68
  badges.push(_jsx(Display.Badge, { className: "self-end", label: statusBadge.label, variant: variant }, "status"));
@@ -78,13 +78,13 @@ function EventRow({ item, onRetry }) {
78
78
  if (!showPayload) {
79
79
  return null;
80
80
  }
81
- return (_jsxs(View, { className: "bg-gray-50 rounded-lg p-2 border border-gray-200 mb-2", children: [_jsx(Typography.Text, { className: "text-gray-700 mb-1", semibold: true, variant: "bodySmall", children: "Payload" }), _jsx(JsonPreview, { selectedJson: payloadString })] }));
81
+ return (_jsxs(View, { className: "bg-bg-muted rounded-lg p-2 border border-border-default mb-2", children: [_jsx(Typography.Text, { className: "text-text-primary mb-1", semibold: true, variant: "bodySmall", children: "Payload" }), _jsx(JsonPreview, { selectedJson: payloadString })] }));
82
82
  }
83
83
  function renderActions() {
84
84
  return (_jsxs(View, { className: "mt-2 gap-2", children: [_jsx(Form.Button, { className: "w-full", onPress: () => setShowPayload((v) => !v), size: "small", title: showPayload ? "Ocultar payload" : "Ver payload", variant: "secondary" }), _jsx(Form.Button, { className: "w-full", onPress: async () => {
85
85
  await Clipboard.setStringAsync(payloadString);
86
86
  }, size: "small", title: "Copiar payload", variant: "secondary" }), _jsx(Form.Button, { className: "w-full", disabled: sending, loading: sending, onPress: handleRetry, size: "medium", title: sending ? "Reenviando…" : "Re-disparar", variant: "primary" })] }));
87
87
  }
88
- return (_jsxs(View, { className: "bg-white border border-gray-200 rounded-2xl mx-4 p-3.5 mb-3 shadow-sm", children: [_jsxs(View, { className: "flex-row items-start mb-2", children: [_jsxs(View, { className: "flex-1 pr-2", children: [_jsx(Typography.Text, { className: "mb-1", numberOfLines: 2, semibold: true, variant: "body", children: friendlyName }), _jsx(Display.CopyableText, { value: item.name })] }), _jsx(View, { className: "ml-2 self-start", children: renderBadges() })] }), renderMeta(), renderPayload(), renderActions()] }));
88
+ return (_jsxs(View, { className: "bg-bg-card border border-border-default rounded-2xl mx-4 p-3.5 mb-3 shadow-sm", children: [_jsxs(View, { className: "flex-row items-start mb-2", children: [_jsxs(View, { className: "flex-1 pr-2", children: [_jsx(Typography.Text, { className: "mb-1", numberOfLines: 2, semibold: true, variant: "body", children: friendlyName }), _jsx(Display.CopyableText, { value: item.name })] }), _jsx(View, { className: "ml-2 self-start", children: renderBadges() })] }), renderMeta(), renderPayload(), renderActions()] }));
89
89
  }
90
90
  export default EventRow;
@@ -1,11 +1,12 @@
1
- import { jsx as _jsx } from "nativewind/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
2
  import { loggerUseCases } from "../../../infrastructure/logger";
3
3
  import { webViewService } from "../../../services/webview.service";
4
+ import { useToast } from "@sincpro/mobile-ui/Feedback";
4
5
  import Spinner from "@sincpro/mobile-ui/Feedback/Feedback.Spinner";
6
+ import { useBottomInset } from "@sincpro/mobile-ui/layouts/BottomInset";
5
7
  import { useCallback, useEffect, useRef, useState } from "react";
6
8
  import { BackHandler, StyleSheet, View } from "react-native";
7
9
  import { SafeAreaView } from "react-native-safe-area-context";
8
- import Toast from "react-native-toast-message";
9
10
  import WebView from "react-native-webview";
10
11
  function WebViewControls({ canGoBack: _canGoBack, canGoForward: _canGoForward, onGoBack: _onGoBack, onGoForward: _onGoForward, onReload: _onReload, }) {
11
12
  return _jsx(View, {});
@@ -14,6 +15,7 @@ function WebViewLoadingIndicator({ backgroundColor }) {
14
15
  return (_jsx(View, { style: [StyleSheet.absoluteFillObject, styles.loading, { backgroundColor }], children: _jsx(Spinner, { size: "large", text: "Cargando p\u00E1gina..." }) }));
15
16
  }
16
17
  function useWebView() {
18
+ const toast = useToast();
17
19
  const webViewRef = useRef(null);
18
20
  const [canGoBack, setCanGoBack] = useState(false);
19
21
  const [retryCount, setRetryCount] = useState(0);
@@ -37,11 +39,11 @@ function useWebView() {
37
39
  const hardRefresh = useCallback(() => {
38
40
  if (webViewRef.current) {
39
41
  loggerUseCases.info("Hard refresh: Clearing cache and reloading");
40
- Toast.show({
41
- type: "info",
42
- text1: "Limpiando caché...",
43
- text2: "Descargando cliente fresco",
44
- visibilityTime: 2000,
42
+ toast.show({
43
+ variant: "info",
44
+ title: "Limpiando caché...",
45
+ message: "Descargando cliente fresco",
46
+ duration: 2000,
45
47
  });
46
48
  if (webViewRef.current.clearCache) {
47
49
  webViewRef.current.clearCache(true);
@@ -52,7 +54,7 @@ function useWebView() {
52
54
  }
53
55
  }, 300);
54
56
  }
55
- }, []);
57
+ }, [toast]);
56
58
  useEffect(() => {
57
59
  const backHandler = BackHandler.addEventListener("hardwareBackPress", () => {
58
60
  if (canGoBack) {
@@ -88,7 +90,9 @@ function useWebView() {
88
90
  }
89
91
  function InjectableWebView({ url, scripts, injectedJavaScript, onMessage, onNavigationChange, onLoadStart, onLoadEnd, onError, renderLoading, backgroundColor = "#f8f8f8", reloadKey, hardRefreshKey, maxRetries = 2, useDefaultCache = true, }) {
90
92
  const { webViewRef, handleNavigationStateChange, retryCount, setRetryCount, reload, hardRefresh, errorTimeoutRef, loadingTimeoutRef, isLoadingRef, } = useWebView();
93
+ const bottomInset = useBottomInset();
91
94
  const finalScript = webViewService.combineScripts(scripts, injectedJavaScript);
95
+ const [isLoading, setIsLoading] = useState(true);
92
96
  function handleMessage(event) {
93
97
  onMessage?.(event);
94
98
  }
@@ -98,6 +102,7 @@ function InjectableWebView({ url, scripts, injectedJavaScript, onMessage, onNavi
98
102
  }
99
103
  function handleLoadStart() {
100
104
  isLoadingRef.current = true;
105
+ setIsLoading(true);
101
106
  if (loadingTimeoutRef.current) {
102
107
  clearTimeout(loadingTimeoutRef.current);
103
108
  }
@@ -111,6 +116,7 @@ function InjectableWebView({ url, scripts, injectedJavaScript, onMessage, onNavi
111
116
  }
112
117
  function handleLoadEnd() {
113
118
  isLoadingRef.current = false;
119
+ setIsLoading(false);
114
120
  if (loadingTimeoutRef.current) {
115
121
  clearTimeout(loadingTimeoutRef.current);
116
122
  loadingTimeoutRef.current = null;
@@ -156,8 +162,12 @@ function InjectableWebView({ url, scripts, injectedJavaScript, onMessage, onNavi
156
162
  }
157
163
  }, [hardRefreshKey, hardRefresh]);
158
164
  const cacheMode = useDefaultCache ? "LOAD_DEFAULT" : "LOAD_NO_CACHE";
159
- return (_jsx(SafeAreaView, { className: "flex-1", edges: ["top", "bottom"], style: { backgroundColor }, children: _jsx(WebView, { allowsBackForwardNavigationGestures: true, cacheEnabled: true, cacheMode: cacheMode, domStorageEnabled: true, injectedJavaScript: finalScript, javaScriptEnabled: true, mixedContentMode: "compatibility", onError: handleError, onLoadEnd: handleLoadEnd, onLoadStart: handleLoadStart, onMessage: handleMessage, onNavigationStateChange: handleNavigationStateChangeInternal, ref: webViewRef, renderLoading: renderLoading ||
160
- (() => _jsx(WebViewLoadingIndicator, { backgroundColor: backgroundColor })), scalesPageToFit: true, sharedCookiesEnabled: true, source: { uri: url }, startInLoadingState: true, style: { flex: 1, backgroundColor } }) }));
165
+ return (
166
+ // Outer full-bleed container: under edge-to-edge (SDK 54+ / targetSdk 36) the window
167
+ // draws behind the transparent system bars. The loading overlay lives here (NOT inside
168
+ // the inset SafeAreaView) so it covers the whole screen and also shows on reload/refresh.
169
+ _jsxs(View, { style: { flex: 1, backgroundColor }, children: [_jsx(SafeAreaView, { className: "flex-1", edges: ["bottom"], style: { backgroundColor, marginBottom: bottomInset }, children: _jsx(WebView, { allowsBackForwardNavigationGestures: true, cacheEnabled: true, cacheMode: cacheMode, domStorageEnabled: true, injectedJavaScript: finalScript, javaScriptEnabled: true, mixedContentMode: "compatibility", onError: handleError, onLoadEnd: handleLoadEnd, onLoadStart: handleLoadStart, onMessage: handleMessage, onNavigationStateChange: handleNavigationStateChangeInternal, ref: webViewRef, scalesPageToFit: true, sharedCookiesEnabled: true, source: { uri: url }, style: { flex: 1, backgroundColor } }) }), isLoading &&
170
+ (renderLoading ? (renderLoading()) : (_jsx(WebViewLoadingIndicator, { backgroundColor: backgroundColor })))] }));
161
171
  }
162
172
  const styles = StyleSheet.create({
163
173
  loading: {
@@ -14,6 +14,6 @@ function TableInfoRow({ item, onPress }) {
14
14
  messageId += `[${parsedData.uuid})]`;
15
15
  }
16
16
  const jsonString = safeJsonStringify(parsedData);
17
- return (_jsx(TouchableOpacity, { activeOpacity: 0.7, className: "bg-white p-4 rounded-xl my-2 shadow-sm", onPress: onPress, children: _jsxs(View, { children: [_jsx(Typography.Text, { className: "mb-2", variant: "caption", children: parsedData.name || "Sin Nombre" }), _jsx(Typography.Text, { numberOfLines: 1, semibold: true, variant: "body", children: messageId || "Sin Identificador" }), parsedData.status && (_jsxs(Typography.Text, { variant: "label", children: ["Estado: ", parsedData.status] })), _jsx(Typography.Text, { className: "text-gray-500", ellipsizeMode: "tail", numberOfLines: 2, variant: "bodySmall", children: jsonString })] }) }));
17
+ return (_jsx(TouchableOpacity, { activeOpacity: 0.7, className: "bg-bg-card p-4 rounded-xl my-2 shadow-sm", onPress: onPress, children: _jsxs(View, { children: [_jsx(Typography.Text, { className: "mb-2", variant: "caption", children: parsedData.name || "Sin Nombre" }), _jsx(Typography.Text, { numberOfLines: 1, semibold: true, variant: "body", children: messageId || "Sin Identificador" }), parsedData.status && (_jsxs(Typography.Text, { variant: "label", children: ["Estado: ", parsedData.status] })), _jsx(Typography.Text, { className: "text-text-secondary", ellipsizeMode: "tail", numberOfLines: 2, variant: "bodySmall", children: jsonString })] }) }));
18
18
  }
19
19
  export default TableInfoRow;
@@ -1,19 +1,19 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "nativewind/jsx-runtime";
2
2
  import { Display } from "@sincpro/mobile-ui/Display";
3
3
  import { Form } from "@sincpro/mobile-ui/Form";
4
- import { theme } from "@sincpro/mobile-ui/theme";
4
+ import { useTheme } from "@sincpro/mobile-ui/theme";
5
5
  import { Typography } from "@sincpro/mobile-ui/Typography";
6
6
  import { FormViewV2 } from "@sincpro/mobile-ui/views/FormViewV2";
7
7
  import JsonPreview from "@sincpro/mobile-ui/widgets/JSONViewer";
8
- import { EVariantScreenHeader } from "@sincpro/mobile-ui/widgets/ScreenHeader";
9
8
  import * as Clipboard from "expo-clipboard";
10
9
  import { View } from "react-native";
11
10
  import { useDatabase } from "./database.context";
12
11
  const Button = Form.Button;
13
12
  const Icon = Display.Icon;
14
13
  function JsonDetailView() {
14
+ const theme = useTheme();
15
15
  const { selectedTable, selectedRowJson, goBackFromJson, currentRowIndex, filteredData, hasNextRow, hasPreviousRow, goToNextRow, goToPreviousRow, } = useDatabase();
16
- return (_jsxs(FormViewV2.Root, { description: `JSON VIEWER - ${selectedTable}`, isLoading: false, item: { json: selectedRowJson }, name: "Detalles", onBack: goBackFromJson, children: [_jsx(FormViewV2.Header, { variant: EVariantScreenHeader.FLAT_HEADER, children: _jsx(FormViewV2.Header.Actions, { children: _jsxs(Typography.Text, { className: "text-gray-500", variant: "bodySmall", children: [currentRowIndex + 1, " de ", filteredData.length] }) }) }), _jsx(FormViewV2.Content, { children: _jsx(JsonPreview, { selectedJson: selectedRowJson }) }), _jsx(FormViewV2.Footer, { children: _jsxs(View, { className: "flex-row items-center justify-between gap-3", children: [_jsx(Button, { className: "flex-none min-w-[50px]", disabled: !hasPreviousRow, icon: _jsx(Icon, { color: hasPreviousRow ? theme.secondary : theme.text.tertiary, name: "chevron-left", size: 24, type: "feather" }), onPress: goToPreviousRow, title: "", variant: "secondary" }), _jsx(Button, { className: "flex-1", onPress: async () => {
16
+ return (_jsxs(FormViewV2.Root, { description: `JSON VIEWER - ${selectedTable}`, isLoading: false, item: { json: selectedRowJson }, name: "Detalles", onBack: goBackFromJson, children: [_jsx(FormViewV2.Header, { variant: "default", children: _jsx(FormViewV2.Header.Actions, { children: _jsxs(Typography.Text, { className: "text-text-secondary", variant: "bodySmall", children: [currentRowIndex + 1, " de ", filteredData.length] }) }) }), _jsx(FormViewV2.Content, { children: _jsx(JsonPreview, { selectedJson: selectedRowJson }) }), _jsx(FormViewV2.Footer, { children: _jsxs(View, { className: "flex-row items-center justify-between gap-3", children: [_jsx(Button, { className: "flex-none min-w-[50px]", disabled: !hasPreviousRow, icon: _jsx(Icon, { color: hasPreviousRow ? theme.secondary : theme.text.tertiary, name: "chevron-left", size: 24, type: "feather" }), onPress: goToPreviousRow, title: "", variant: "secondary" }), _jsx(Button, { className: "flex-1", onPress: async () => {
17
17
  await Clipboard.setStringAsync(selectedRowJson);
18
18
  }, title: "Copiar JSON", variant: "primary" }), _jsx(Button, { className: "flex-none min-w-[50px]", disabled: !hasNextRow, icon: _jsx(Icon, { color: hasNextRow ? theme.secondary : theme.text.tertiary, name: "chevron-right", size: 24, type: "feather" }), onPress: goToNextRow, title: "", variant: "secondary" })] }) })] }));
19
19
  }
@@ -1,22 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "nativewind/jsx-runtime";
2
+ import { useNavigation } from "@react-navigation/native";
2
3
  import DatabaseInfoRow from "../../../ui/components/organisms/DatabaseInfoRow";
3
4
  import { ListViewV2 } from "@sincpro/mobile-ui/views/ListViewV2";
4
- import { EVariantScreenHeader } from "@sincpro/mobile-ui/widgets/ScreenHeader";
5
5
  import { useEffect } from "react";
6
- import { useNavigate } from "react-router-native";
7
6
  import { DatabaseProvider, EDatabaseView, useDatabase } from "./database.context";
8
7
  import JsonDetailView from "./database.json_detail";
9
8
  import TableRowsView from "./database.table_rows";
10
9
  function TablesView() {
11
- const navigate = useNavigate();
10
+ const navigation = useNavigation();
12
11
  const { loadTables, isLoading, tables, selectTable } = useDatabase();
13
12
  useEffect(() => {
14
13
  void loadTables();
15
14
  }, [loadTables]);
16
- const onBack = () => navigate(-1);
15
+ const onBack = () => navigation.goBack();
17
16
  return (_jsxs(ListViewV2.Root, { isLoading: isLoading, items: tables, name: "Lista de tablas", onBack: onBack, onPressItem: async (table) => {
18
17
  await selectTable(table.name);
19
- }, children: [_jsx(ListViewV2.Header, { variant: EVariantScreenHeader.FLAT_HEADER }), _jsx(ListViewV2.Content, { children: (table) => (_jsx(DatabaseInfoRow, { item: {
18
+ }, children: [_jsx(ListViewV2.Header, { variant: "default" }), _jsx(ListViewV2.Content, { children: (table) => (_jsx(DatabaseInfoRow, { item: {
20
19
  name: table.displayName || table.name,
21
20
  description: "Tabla de base de datos",
22
21
  tableName: table.name,