@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.
- package/dist/entrypoints/queue/printImage.subscriber.js +3 -3
- package/dist/entrypoints/ui/AppShell.d.ts +7 -0
- package/dist/entrypoints/ui/AppShell.js +25 -9
- package/dist/entrypoints/ui/common_provider.d.ts +6 -1
- package/dist/entrypoints/ui/common_provider.js +45 -1
- package/dist/entrypoints/ui/domain_switcher.js +5 -4
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/infrastructure/ui/ToastHost.d.ts +6 -1
- package/dist/infrastructure/ui/ToastHost.js +18 -9
- package/dist/services/printer.service.d.ts +6 -0
- package/dist/services/printer.service.js +36 -3
- package/dist/ui/components/atoms/DebugBanner.js +4 -8
- package/dist/ui/components/atoms/PrinterIcon.d.ts +4 -0
- package/dist/ui/components/atoms/PrinterIcon.js +4 -0
- package/dist/ui/components/atoms/index.d.ts +1 -0
- package/dist/ui/components/atoms/index.js +1 -0
- package/dist/ui/components/molecules/BluetoothDeviceSelectorModal.js +16 -10
- package/dist/ui/components/molecules/DeadLetterErrorBlock.js +3 -2
- package/dist/ui/components/molecules/EventTimelineItem.js +31 -33
- package/dist/ui/components/molecules/GeoPermissionCard.d.ts +8 -0
- package/dist/ui/components/molecules/GeoPermissionCard.js +21 -0
- package/dist/ui/components/molecules/ProcessToast.d.ts +7 -4
- package/dist/ui/components/molecules/ProcessToast.js +45 -44
- package/dist/ui/components/molecules/ProcessToastProvider.js +1 -1
- package/dist/ui/components/molecules/TimeZoneSelector.d.ts +7 -0
- package/dist/ui/components/molecules/TimeZoneSelector.js +19 -0
- package/dist/ui/components/molecules/TimeZoneSelectorModal.d.ts +9 -0
- package/dist/ui/components/molecules/TimeZoneSelectorModal.js +61 -0
- package/dist/ui/components/molecules/index.d.ts +4 -0
- package/dist/ui/components/molecules/index.js +3 -0
- package/dist/ui/components/molecules/timezone.d.ts +4 -0
- package/dist/ui/components/molecules/timezone.js +1 -0
- package/dist/ui/components/organisms/BluetoothPrinterSelector.d.ts +3 -1
- package/dist/ui/components/organisms/BluetoothPrinterSelector.js +29 -9
- package/dist/ui/components/organisms/DatabaseInfoRow.js +1 -1
- package/dist/ui/components/organisms/DeadLetterQueueRow.js +3 -3
- package/dist/ui/components/organisms/EventRow.js +15 -15
- package/dist/ui/components/organisms/InjectableWebView.js +20 -10
- package/dist/ui/components/organisms/TableInfoInfoRow.js +1 -1
- package/dist/ui/screens/database/database.json_detail.js +3 -3
- package/dist/ui/screens/database/database.list.js +4 -5
- package/dist/ui/screens/database/database.table_rows.js +1 -2
- package/dist/ui/screens/dead_letter_queue/dead_letter_queue.list.js +4 -5
- package/dist/ui/screens/events/events.list.js +4 -5
- package/package.json +9 -5
- package/dist/ui/layouts/router_layouts.d.ts +0 -17
- 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 {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
return
|
|
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, {
|
|
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 @@
|
|
|
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
|
|
6
|
-
import
|
|
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-
|
|
15
|
-
false: "text-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
21
|
-
textClass: "text-
|
|
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-
|
|
28
|
-
textClass: "text-
|
|
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-
|
|
35
|
-
textClass: "text-
|
|
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-
|
|
41
|
-
textClass: "text-
|
|
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-
|
|
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-
|
|
63
|
-
"bg-
|
|
64
|
-
"bg-
|
|
65
|
-
"bg-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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 (
|
|
160
|
-
|
|
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-
|
|
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 {
|
|
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:
|
|
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
|
|
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 = () =>
|
|
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:
|
|
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,
|