@acvl/mobile-components 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -1
- package/lib/module/camara/CampoDeFotos.js +4 -2
- package/lib/module/camara/CampoDeFotos.js.map +1 -1
- package/lib/module/camara/elegir.js +4 -3
- package/lib/module/camara/elegir.js.map +1 -1
- package/lib/module/componentes/AccionesDelEncabezado.js +3 -1
- package/lib/module/componentes/AccionesDelEncabezado.js.map +1 -1
- package/lib/module/componentes/BotonDeAvatar.js +24 -20
- package/lib/module/componentes/BotonDeAvatar.js.map +1 -1
- package/lib/module/componentes/BotonFlotante.js +71 -0
- package/lib/module/componentes/BotonFlotante.js.map +1 -0
- package/lib/module/componentes/CabeceraEmpujada.js +3 -1
- package/lib/module/componentes/CabeceraEmpujada.js.map +1 -1
- package/lib/module/componentes/PantallaDeError.js +6 -4
- package/lib/module/componentes/PantallaDeError.js.map +1 -1
- package/lib/module/componentes/TiraDeFotos.js +4 -2
- package/lib/module/componentes/TiraDeFotos.js.map +1 -1
- package/lib/module/componentes/VisorDeFoto.js +6 -4
- package/lib/module/componentes/VisorDeFoto.js.map +1 -1
- package/lib/module/dialogos/HostDeDialogos.js +3 -1
- package/lib/module/dialogos/HostDeDialogos.js.map +1 -1
- package/lib/module/dialogos/dialogos.js +6 -4
- package/lib/module/dialogos/dialogos.js.map +1 -1
- package/lib/module/formularios/SelectorDeOpcion.js +13 -7
- package/lib/module/formularios/SelectorDeOpcion.js.map +1 -1
- package/lib/module/formularios/campos.js +9 -4
- package/lib/module/formularios/campos.js.map +1 -1
- package/lib/module/formularios/guardado.js +4 -2
- package/lib/module/formularios/guardado.js.map +1 -1
- package/lib/module/fotos/elegir.js +10 -8
- package/lib/module/fotos/elegir.js.map +1 -1
- package/lib/module/hoja/HojaDeLista.js +5 -3
- package/lib/module/hoja/HojaDeLista.js.map +1 -1
- package/lib/module/hoja/useMasDeLaFicha.js +3 -1
- package/lib/module/hoja/useMasDeLaFicha.js.map +1 -1
- package/lib/module/idioma/ProveedorDeIdioma.js +54 -0
- package/lib/module/idioma/ProveedorDeIdioma.js.map +1 -0
- package/lib/module/idioma/index.js +4 -0
- package/lib/module/idioma/index.js.map +1 -0
- package/lib/module/idioma/textos.js +237 -0
- package/lib/module/idioma/textos.js.map +1 -0
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/listas/HojaDeFiltros.js +11 -8
- package/lib/module/listas/HojaDeFiltros.js.map +1 -1
- package/lib/module/listas/ListaPaginada.js +6 -4
- package/lib/module/listas/ListaPaginada.js.map +1 -1
- package/lib/module/listas/piezas.js +13 -7
- package/lib/module/listas/piezas.js.map +1 -1
- package/lib/module/pantallas/PantallaDeAcceso.js +6 -4
- package/lib/module/pantallas/PantallaDeAcceso.js.map +1 -1
- package/lib/module/pantallas/PantallaDeEdicion.js +9 -6
- package/lib/module/pantallas/PantallaDeEdicion.js.map +1 -1
- package/lib/module/pantallas/PantallaDeFicha.js +7 -5
- package/lib/module/pantallas/PantallaDeFicha.js.map +1 -1
- package/lib/module/pantallas/PantallaDeHistorial.js +18 -14
- package/lib/module/pantallas/PantallaDeHistorial.js.map +1 -1
- package/lib/module/pantallas/PantallaDePerfil.js +12 -10
- package/lib/module/pantallas/PantallaDePerfil.js.map +1 -1
- package/lib/typescript/src/camara/CampoDeFotos.d.ts.map +1 -1
- package/lib/typescript/src/camara/elegir.d.ts.map +1 -1
- package/lib/typescript/src/componentes/AccionesDelEncabezado.d.ts.map +1 -1
- package/lib/typescript/src/componentes/BotonDeAvatar.d.ts.map +1 -1
- package/lib/typescript/src/componentes/BotonFlotante.d.ts +21 -0
- package/lib/typescript/src/componentes/BotonFlotante.d.ts.map +1 -0
- package/lib/typescript/src/componentes/CabeceraEmpujada.d.ts.map +1 -1
- package/lib/typescript/src/componentes/PantallaDeError.d.ts.map +1 -1
- package/lib/typescript/src/componentes/TiraDeFotos.d.ts.map +1 -1
- package/lib/typescript/src/componentes/VisorDeFoto.d.ts.map +1 -1
- package/lib/typescript/src/dialogos/HostDeDialogos.d.ts.map +1 -1
- package/lib/typescript/src/dialogos/dialogos.d.ts.map +1 -1
- package/lib/typescript/src/formularios/SelectorDeOpcion.d.ts.map +1 -1
- package/lib/typescript/src/formularios/campos.d.ts.map +1 -1
- package/lib/typescript/src/formularios/guardado.d.ts.map +1 -1
- package/lib/typescript/src/fotos/elegir.d.ts.map +1 -1
- package/lib/typescript/src/hoja/HojaDeLista.d.ts.map +1 -1
- package/lib/typescript/src/hoja/useMasDeLaFicha.d.ts.map +1 -1
- package/lib/typescript/src/idioma/ProveedorDeIdioma.d.ts +16 -0
- package/lib/typescript/src/idioma/ProveedorDeIdioma.d.ts.map +1 -0
- package/lib/typescript/src/idioma/index.d.ts +3 -0
- package/lib/typescript/src/idioma/index.d.ts.map +1 -0
- package/lib/typescript/src/idioma/textos.d.ts +124 -0
- package/lib/typescript/src/idioma/textos.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/listas/HojaDeFiltros.d.ts.map +1 -1
- package/lib/typescript/src/listas/ListaPaginada.d.ts.map +1 -1
- package/lib/typescript/src/listas/piezas.d.ts.map +1 -1
- package/lib/typescript/src/pantallas/PantallaDeAcceso.d.ts.map +1 -1
- package/lib/typescript/src/pantallas/PantallaDeEdicion.d.ts.map +1 -1
- package/lib/typescript/src/pantallas/PantallaDeFicha.d.ts.map +1 -1
- package/lib/typescript/src/pantallas/PantallaDeHistorial.d.ts.map +1 -1
- package/lib/typescript/src/pantallas/PantallaDePerfil.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/camara/CampoDeFotos.tsx +4 -2
- package/src/camara/elegir.ts +4 -3
- package/src/componentes/AccionesDelEncabezado.tsx +3 -1
- package/src/componentes/BotonDeAvatar.tsx +22 -18
- package/src/componentes/BotonFlotante.tsx +65 -0
- package/src/componentes/CabeceraEmpujada.tsx +3 -1
- package/src/componentes/PantallaDeError.tsx +6 -4
- package/src/componentes/TiraDeFotos.tsx +4 -2
- package/src/componentes/VisorDeFoto.tsx +7 -7
- package/src/dialogos/HostDeDialogos.tsx +3 -1
- package/src/dialogos/dialogos.ts +15 -4
- package/src/formularios/SelectorDeOpcion.tsx +22 -8
- package/src/formularios/campos.tsx +9 -4
- package/src/formularios/guardado.ts +4 -2
- package/src/fotos/elegir.ts +14 -10
- package/src/hoja/HojaDeLista.tsx +5 -5
- package/src/hoja/useMasDeLaFicha.tsx +3 -1
- package/src/idioma/ProveedorDeIdioma.tsx +51 -0
- package/src/idioma/index.ts +7 -0
- package/src/idioma/textos.ts +246 -0
- package/src/index.ts +12 -0
- package/src/listas/HojaDeFiltros.tsx +10 -9
- package/src/listas/ListaPaginada.tsx +6 -6
- package/src/listas/piezas.tsx +17 -10
- package/src/pantallas/PantallaDeAcceso.tsx +6 -4
- package/src/pantallas/PantallaDeEdicion.tsx +13 -6
- package/src/pantallas/PantallaDeFicha.tsx +7 -5
- package/src/pantallas/PantallaDeHistorial.tsx +22 -16
- package/src/pantallas/PantallaDePerfil.tsx +12 -10
|
@@ -6,6 +6,7 @@ import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
|
|
6
6
|
import React from "react";
|
|
7
7
|
import { Modal, Pressable, ScrollView, Switch, TouchableOpacity, View } from "react-native";
|
|
8
8
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
9
|
+
import { useTextos } from "../idioma";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* La hoja de filtros y orden de una lista (viajes, recursos), a pantalla completa
|
|
@@ -35,17 +36,18 @@ export default function HojaDeFiltros({
|
|
|
35
36
|
const c = usePaleta();
|
|
36
37
|
const styles = useStyles();
|
|
37
38
|
const insets = useSafeAreaInsets();
|
|
39
|
+
const t = useTextos();
|
|
38
40
|
return (
|
|
39
41
|
<Modal visible={abierta} animationType="slide" onRequestClose={onCerrar}>
|
|
40
42
|
<View style={[styles.hoja, { paddingTop: insets.top + 10 }]}>
|
|
41
43
|
<View style={styles.cabecera}>
|
|
42
|
-
<TouchableOpacity onPress={onCerrar} accessibilityLabel=
|
|
44
|
+
<TouchableOpacity onPress={onCerrar} accessibilityLabel={t.comun.cerrar}>
|
|
43
45
|
<MaterialIcons name="arrow-back" size={45} color={c.primary} />
|
|
44
46
|
</TouchableOpacity>
|
|
45
|
-
<Text style={styles.titulo}>
|
|
47
|
+
<Text style={styles.titulo}>{t.listas.filtros}</Text>
|
|
46
48
|
{hayAlgo ? (
|
|
47
49
|
<TouchableOpacity onPress={onQuitar}>
|
|
48
|
-
<Text style={styles.limpiar}>
|
|
50
|
+
<Text style={styles.limpiar}>{t.comun.quitar}</Text>
|
|
49
51
|
</TouchableOpacity>
|
|
50
52
|
) : null}
|
|
51
53
|
</View>
|
|
@@ -54,9 +56,7 @@ export default function HojaDeFiltros({
|
|
|
54
56
|
|
|
55
57
|
<View style={{ paddingBottom: insets.bottom + 16 }}>
|
|
56
58
|
<BotonGrande onPress={onCerrar}>
|
|
57
|
-
{visibles === null
|
|
58
|
-
? "Listo"
|
|
59
|
-
: `Ver ${visibles} ${visibles === 1 ? nombres[0] : nombres[1]}`}
|
|
59
|
+
{visibles === null ? t.comun.listo : t.listas.ver(visibles, nombres)}
|
|
60
60
|
</BotonGrande>
|
|
61
61
|
</View>
|
|
62
62
|
</View>
|
|
@@ -205,17 +205,18 @@ export function OrdenDeLista({
|
|
|
205
205
|
onOrdenar: (orden: string | null) => void;
|
|
206
206
|
porOmision?: string;
|
|
207
207
|
}) {
|
|
208
|
+
const t = useTextos();
|
|
208
209
|
const descendente = orden?.startsWith("-") ?? false;
|
|
209
210
|
const campo = orden?.replace(/^-/, "");
|
|
210
211
|
const elegido = campos.find((c) => c.value === campo);
|
|
211
|
-
const sentidos = elegido?.sentidos ?? { asc:
|
|
212
|
+
const sentidos = elegido?.sentidos ?? { asc: t.listas.ascendente, desc: t.listas.descendente };
|
|
212
213
|
// El sentido con el que arranca va primero: en una fecha se lee «Recientes primero» antes
|
|
213
214
|
// que «Antiguos primero».
|
|
214
215
|
const ordenDeSentidos: ("asc" | "desc")[] =
|
|
215
216
|
elegido?.primero === "desc" ? ["desc", "asc"] : ["asc", "desc"];
|
|
216
217
|
return (
|
|
217
218
|
<>
|
|
218
|
-
<SeccionDeFiltros>
|
|
219
|
+
<SeccionDeFiltros>{t.listas.ordenarPor}</SeccionDeFiltros>
|
|
219
220
|
{porOmision ? (
|
|
220
221
|
<OpcionDeFiltro
|
|
221
222
|
texto={porOmision}
|
|
@@ -240,7 +241,7 @@ export function OrdenDeLista({
|
|
|
240
241
|
))}
|
|
241
242
|
{campo !== undefined ? (
|
|
242
243
|
<>
|
|
243
|
-
<SeccionDeFiltros>
|
|
244
|
+
<SeccionDeFiltros>{t.listas.sentido}</SeccionDeFiltros>
|
|
244
245
|
{ordenDeSentidos.map((sentido) => (
|
|
245
246
|
<OpcionDeFiltro
|
|
246
247
|
key={sentido}
|
|
@@ -7,6 +7,7 @@ import type MaterialCommunityIcons from "@expo/vector-icons/MaterialCommunityIco
|
|
|
7
7
|
import React from "react";
|
|
8
8
|
import { ActivityIndicator, FlatList, RefreshControl } from "react-native";
|
|
9
9
|
import { renglonesDe, type Pagina } from "./paginado";
|
|
10
|
+
import { useTextos } from "../idioma";
|
|
10
11
|
|
|
11
12
|
/** Lo que la lista lee de un `use…InfiniteQuery` de RTK Query. */
|
|
12
13
|
export interface ConsultaPaginada<T> {
|
|
@@ -50,6 +51,7 @@ export default function ListaPaginada<T>({
|
|
|
50
51
|
}: Props<T>) {
|
|
51
52
|
const c = usePaleta();
|
|
52
53
|
const styles = useStyles();
|
|
54
|
+
const t = useTextos();
|
|
53
55
|
const renglones = renglonesDe(consulta.data);
|
|
54
56
|
const total = consulta.data?.pages[0]?.count;
|
|
55
57
|
|
|
@@ -60,11 +62,11 @@ export default function ListaPaginada<T>({
|
|
|
60
62
|
return (
|
|
61
63
|
<EstadoVacio
|
|
62
64
|
icono="cloud-off-outline"
|
|
63
|
-
titulo=
|
|
64
|
-
texto=
|
|
65
|
+
titulo={t.comun.noSePudoCargar}
|
|
66
|
+
texto={t.comun.revisaLaSenal}
|
|
65
67
|
>
|
|
66
68
|
<BotonGrande icono="refresh" onPress={() => consulta.refetch()}>
|
|
67
|
-
|
|
69
|
+
{t.comun.reintentar}
|
|
68
70
|
</BotonGrande>
|
|
69
71
|
</EstadoVacio>
|
|
70
72
|
);
|
|
@@ -78,9 +80,7 @@ export default function ListaPaginada<T>({
|
|
|
78
80
|
contentContainerStyle={[styles.contenido, !renglones.length && styles.vacia]}
|
|
79
81
|
ListHeaderComponent={
|
|
80
82
|
total !== undefined && renglones.length ? (
|
|
81
|
-
<Text style={styles.total}>
|
|
82
|
-
{total === 1 ? `1 ${nombres[0]}` : `${total} ${nombres[1]}`}
|
|
83
|
-
</Text>
|
|
83
|
+
<Text style={styles.total}>{t.listas.cuantos(total, nombres)}</Text>
|
|
84
84
|
) : null
|
|
85
85
|
}
|
|
86
86
|
ListEmptyComponent={
|
package/src/listas/piezas.tsx
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type TextInputProps,
|
|
21
21
|
type ViewStyle,
|
|
22
22
|
} from "react-native";
|
|
23
|
+
import { useTextos } from "../idioma";
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Las piezas de las listas y las fichas: la cabecera de una lista (buscador y pestañas), la
|
|
@@ -58,6 +59,7 @@ export function Buscador({
|
|
|
58
59
|
}) {
|
|
59
60
|
const c = usePaleta();
|
|
60
61
|
const styles = useStyles();
|
|
62
|
+
const t = useTextos();
|
|
61
63
|
const Campo = Entrada ?? TextInput;
|
|
62
64
|
return (
|
|
63
65
|
<View style={[styles.buscador, style]}>
|
|
@@ -90,7 +92,7 @@ export function Buscador({
|
|
|
90
92
|
<Pressable
|
|
91
93
|
onPress={() => onCambiar("")}
|
|
92
94
|
hitSlop={8}
|
|
93
|
-
accessibilityLabel=
|
|
95
|
+
accessibilityLabel={t.listas.borrarBusqueda}
|
|
94
96
|
>
|
|
95
97
|
<MaterialCommunityIcons name="close-circle" size={22} color={c.secondary} />
|
|
96
98
|
</Pressable>
|
|
@@ -114,13 +116,14 @@ export interface FiltrosDeCabecera {
|
|
|
114
116
|
*/
|
|
115
117
|
function BotonDeFiltros({ cuenta, punto, onPress }: FiltrosDeCabecera) {
|
|
116
118
|
const c = usePaleta();
|
|
119
|
+
const t = useTextos();
|
|
117
120
|
const styles = useStyles();
|
|
118
121
|
const activo = Boolean(cuenta) || punto;
|
|
119
122
|
return (
|
|
120
123
|
<Pressable
|
|
121
124
|
onPress={onPress}
|
|
122
125
|
accessibilityRole="button"
|
|
123
|
-
accessibilityLabel={cuenta ?
|
|
126
|
+
accessibilityLabel={cuenta ? t.listas.filtrosPuestos(cuenta) : t.listas.filtrosYOrden}
|
|
124
127
|
hitSlop={4}
|
|
125
128
|
style={({ pressed }) => [
|
|
126
129
|
styles.botonDeFiltros,
|
|
@@ -361,6 +364,7 @@ export function RenglonDeContacto({
|
|
|
361
364
|
}) {
|
|
362
365
|
const c = usePaleta();
|
|
363
366
|
const styles = useStyles();
|
|
367
|
+
const t = useTextos();
|
|
364
368
|
const boton = (
|
|
365
369
|
icono: React.ComponentProps<typeof MaterialCommunityIcons>["name"],
|
|
366
370
|
etiqueta: string,
|
|
@@ -386,15 +390,17 @@ export function RenglonDeContacto({
|
|
|
386
390
|
{correo ? <Text style={styles.linea}>{correo}</Text> : null}
|
|
387
391
|
</View>
|
|
388
392
|
<View style={styles.acciones}>
|
|
389
|
-
{telefono ? boton("phone",
|
|
393
|
+
{telefono ? boton("phone", t.contacto.llamar(nombre), `tel:${telefono}`) : null}
|
|
390
394
|
{telefono
|
|
391
395
|
? boton(
|
|
392
396
|
"whatsapp",
|
|
393
|
-
|
|
397
|
+
t.contacto.whatsapp(nombre),
|
|
394
398
|
`https://wa.me/${digitos(telefono)}`,
|
|
395
399
|
)
|
|
396
400
|
: null}
|
|
397
|
-
{correo
|
|
401
|
+
{correo
|
|
402
|
+
? boton("email-outline", t.contacto.correo(nombre), `mailto:${correo}`)
|
|
403
|
+
: null}
|
|
398
404
|
</View>
|
|
399
405
|
</View>
|
|
400
406
|
);
|
|
@@ -418,7 +424,7 @@ const abrirEnMapas = (direccion: string) => {
|
|
|
418
424
|
* no hay renglón. Es el hermano de `RenglonDeContacto`.
|
|
419
425
|
*/
|
|
420
426
|
export function RenglonDeDireccion({
|
|
421
|
-
etiqueta
|
|
427
|
+
etiqueta,
|
|
422
428
|
lineas,
|
|
423
429
|
enHoja,
|
|
424
430
|
}: {
|
|
@@ -430,13 +436,14 @@ export function RenglonDeDireccion({
|
|
|
430
436
|
}) {
|
|
431
437
|
const c = usePaleta();
|
|
432
438
|
const styles = useStyles();
|
|
439
|
+
const t = useTextos();
|
|
433
440
|
const presentes = lineas.map((l) => l?.trim()).filter((l): l is string => Boolean(l));
|
|
434
441
|
if (!presentes.length) return null;
|
|
435
442
|
return (
|
|
436
443
|
<Pressable
|
|
437
444
|
onPress={() => abrirEnMapas(presentes.join(", "))}
|
|
438
445
|
accessibilityRole="link"
|
|
439
|
-
accessibilityLabel={
|
|
446
|
+
accessibilityLabel={t.contacto.abrirEnElMapa(presentes.join(", "))}
|
|
440
447
|
style={({ pressed }) => [
|
|
441
448
|
styles.renglon,
|
|
442
449
|
enHoja && styles.renglonEnHoja,
|
|
@@ -444,7 +451,7 @@ export function RenglonDeDireccion({
|
|
|
444
451
|
]}
|
|
445
452
|
>
|
|
446
453
|
<View style={styles.renglonTexto}>
|
|
447
|
-
<Text style={styles.etiqueta}>{etiqueta}</Text>
|
|
454
|
+
<Text style={styles.etiqueta}>{etiqueta ?? t.contacto.direccion}</Text>
|
|
448
455
|
{presentes.map((l, i) => (
|
|
449
456
|
<Text key={i} style={i === 0 ? styles.valor : styles.linea}>
|
|
450
457
|
{l}
|
|
@@ -544,9 +551,9 @@ export function EstadoDeHoja({
|
|
|
544
551
|
}) {
|
|
545
552
|
const c = usePaleta();
|
|
546
553
|
const styles = useStyles();
|
|
554
|
+
const t = useTextos();
|
|
547
555
|
if (cargando) return <ActivityIndicator color={c.primary} />;
|
|
548
|
-
if (fallo)
|
|
549
|
-
return <Text style={styles.linea}>No se pudo cargar. Jala la ficha para releer.</Text>;
|
|
556
|
+
if (fallo) return <Text style={styles.linea}>{t.ficha.seccionNoCargo}</Text>;
|
|
550
557
|
return vacio ? <Text style={styles.linea}>{vacio}</Text> : null;
|
|
551
558
|
}
|
|
552
559
|
|
|
@@ -10,6 +10,7 @@ import { setErrors } from "../formularios/errores";
|
|
|
10
10
|
import TextoDeAccesoControlado from "../formularios/TextoDeAccesoControlado";
|
|
11
11
|
import { crearEstilos, usePaleta } from "../tema";
|
|
12
12
|
import { Text } from "../texto";
|
|
13
|
+
import { useTextos } from "../idioma";
|
|
13
14
|
|
|
14
15
|
// Mismo corte que ContenidoDePantalla: de aquí para abajo es teléfono.
|
|
15
16
|
const ANCHO_DE_TABLETA = 700;
|
|
@@ -58,6 +59,7 @@ export default function PantallaDeAcceso({
|
|
|
58
59
|
const form = useForm<DatosDeAcceso>({ defaultValues: { username: "", password: "" } });
|
|
59
60
|
const [entrando, setEntrando] = useState(false);
|
|
60
61
|
const [aviso, setAviso] = useState<string | null>(null);
|
|
62
|
+
const t = useTextos();
|
|
61
63
|
|
|
62
64
|
const entrar = form.handleSubmit(async (datos) => {
|
|
63
65
|
setEntrando(true);
|
|
@@ -77,7 +79,7 @@ export default function PantallaDeAcceso({
|
|
|
77
79
|
} catch {
|
|
78
80
|
// `onEntrar` contesta lo que conoce; lo que se le escape no deja el botón cargando.
|
|
79
81
|
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
|
|
80
|
-
setAviso(
|
|
82
|
+
setAviso(t.acceso.noSePudo);
|
|
81
83
|
} finally {
|
|
82
84
|
setEntrando(false);
|
|
83
85
|
}
|
|
@@ -97,13 +99,13 @@ export default function PantallaDeAcceso({
|
|
|
97
99
|
<TextoDeAccesoControlado
|
|
98
100
|
name="username"
|
|
99
101
|
control={form.control}
|
|
100
|
-
etiqueta=
|
|
102
|
+
etiqueta={t.acceso.usuario}
|
|
101
103
|
entrada={{ autoCapitalize: "none" }}
|
|
102
104
|
/>
|
|
103
105
|
<TextoDeAccesoControlado
|
|
104
106
|
name="password"
|
|
105
107
|
control={form.control}
|
|
106
|
-
etiqueta=
|
|
108
|
+
etiqueta={t.acceso.contrasena}
|
|
107
109
|
contrasena
|
|
108
110
|
entrada={{ autoCapitalize: "none" }}
|
|
109
111
|
/>
|
|
@@ -116,7 +118,7 @@ export default function PantallaDeAcceso({
|
|
|
116
118
|
onPress={entrar}
|
|
117
119
|
style={styles.boton}
|
|
118
120
|
>
|
|
119
|
-
|
|
121
|
+
{t.acceso.entrar}
|
|
120
122
|
</Button>
|
|
121
123
|
</>
|
|
122
124
|
);
|
|
@@ -8,6 +8,7 @@ import EstadoVacio from "../componentes/EstadoVacio";
|
|
|
8
8
|
import Nota from "../componentes/Nota";
|
|
9
9
|
import { mensajeDeError } from "../formularios/errores";
|
|
10
10
|
import { crearEstilos, usePaleta } from "../tema";
|
|
11
|
+
import { useTextos } from "../idioma";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* El armazón de una pantalla que edita algo (un viaje, un tracto, una caja, un operador). Dos
|
|
@@ -43,19 +44,24 @@ export function EsperaDeEdicion({
|
|
|
43
44
|
}) {
|
|
44
45
|
const c = usePaleta();
|
|
45
46
|
const styles = useStyles();
|
|
47
|
+
const t = useTextos();
|
|
46
48
|
return (
|
|
47
49
|
<View style={styles.root}>
|
|
48
50
|
<CabeceraEmpujada eyebrow={eyebrow} titulo="…" />
|
|
49
51
|
{!puede ? (
|
|
50
|
-
<EstadoVacio
|
|
52
|
+
<EstadoVacio
|
|
53
|
+
icono="lock-outline"
|
|
54
|
+
titulo={t.edicion.sinPermiso}
|
|
55
|
+
texto={sinPermiso}
|
|
56
|
+
/>
|
|
51
57
|
) : consulta.isError ? (
|
|
52
58
|
<EstadoVacio
|
|
53
59
|
icono="cloud-alert"
|
|
54
|
-
titulo={
|
|
55
|
-
texto={mensajeDeError(consulta.error,
|
|
60
|
+
titulo={t.edicion.noSePudoCargar(queEs)}
|
|
61
|
+
texto={mensajeDeError(consulta.error, t.comun.revisaLaSenal)}
|
|
56
62
|
>
|
|
57
63
|
<BotonGrande icono="refresh" modo="contorno" onPress={consulta.refetch}>
|
|
58
|
-
|
|
64
|
+
{t.comun.reintentar}
|
|
59
65
|
</BotonGrande>
|
|
60
66
|
</EstadoVacio>
|
|
61
67
|
) : (
|
|
@@ -71,7 +77,7 @@ export function FormularioDeEdicion({
|
|
|
71
77
|
error,
|
|
72
78
|
guardando,
|
|
73
79
|
onGuardar,
|
|
74
|
-
textoDeGuardar
|
|
80
|
+
textoDeGuardar,
|
|
75
81
|
children,
|
|
76
82
|
}: {
|
|
77
83
|
eyebrow: string;
|
|
@@ -86,6 +92,7 @@ export function FormularioDeEdicion({
|
|
|
86
92
|
}) {
|
|
87
93
|
const styles = useStyles();
|
|
88
94
|
const insets = useSafeAreaInsets();
|
|
95
|
+
const t = useTextos();
|
|
89
96
|
return (
|
|
90
97
|
<View style={styles.root}>
|
|
91
98
|
<CabeceraEmpujada eyebrow={eyebrow} titulo={titulo} />
|
|
@@ -99,7 +106,7 @@ export function FormularioDeEdicion({
|
|
|
99
106
|
</ScrollView>
|
|
100
107
|
<View style={[styles.pie, { paddingBottom: insets.bottom + 16 }]}>
|
|
101
108
|
<BotonGrande icono="check" onPress={onGuardar} cargando={guardando}>
|
|
102
|
-
{guardando ?
|
|
109
|
+
{guardando ? t.edicion.guardando : (textoDeGuardar ?? t.edicion.guardar)}
|
|
103
110
|
</BotonGrande>
|
|
104
111
|
</View>
|
|
105
112
|
</KeyboardAvoidingView>
|
|
@@ -6,6 +6,7 @@ import BotonGrande from "../componentes/BotonGrande";
|
|
|
6
6
|
import CabeceraEmpujada from "../componentes/CabeceraEmpujada";
|
|
7
7
|
import EstadoVacio from "../componentes/EstadoVacio";
|
|
8
8
|
import { crearEstilos, usePaleta } from "../tema";
|
|
9
|
+
import { useTextos } from "../idioma";
|
|
9
10
|
|
|
10
11
|
/** Lo que la ficha lee de un `use…Query` de RTK Query. */
|
|
11
12
|
interface ConsultaDeFicha {
|
|
@@ -54,6 +55,7 @@ export default function PantallaDeFicha({
|
|
|
54
55
|
const c = usePaleta();
|
|
55
56
|
const styles = useStyles();
|
|
56
57
|
const insets = useSafeAreaInsets();
|
|
58
|
+
const t = useTextos();
|
|
57
59
|
|
|
58
60
|
let cuerpo: React.ReactNode;
|
|
59
61
|
if (consulta.isLoading) {
|
|
@@ -62,17 +64,17 @@ export default function PantallaDeFicha({
|
|
|
62
64
|
cuerpo = esNoEncontrado(consulta.error) ? (
|
|
63
65
|
<EstadoVacio
|
|
64
66
|
icono="file-hidden"
|
|
65
|
-
titulo=
|
|
66
|
-
texto=
|
|
67
|
+
titulo={t.ficha.noExiste}
|
|
68
|
+
texto={t.ficha.noExisteTexto}
|
|
67
69
|
/>
|
|
68
70
|
) : (
|
|
69
71
|
<EstadoVacio
|
|
70
72
|
icono="cloud-off-outline"
|
|
71
|
-
titulo=
|
|
72
|
-
texto=
|
|
73
|
+
titulo={t.comun.noSePudoCargar}
|
|
74
|
+
texto={t.comun.revisaLaSenal}
|
|
73
75
|
>
|
|
74
76
|
<BotonGrande icono="refresh" onPress={() => consulta.refetch()}>
|
|
75
|
-
|
|
77
|
+
{t.comun.reintentar}
|
|
76
78
|
</BotonGrande>
|
|
77
79
|
</EstadoVacio>
|
|
78
80
|
);
|
|
@@ -8,6 +8,7 @@ import EstadoVacio from "../componentes/EstadoVacio";
|
|
|
8
8
|
import Tarjeta from "../componentes/Tarjeta";
|
|
9
9
|
import { crearEstilos, renglon, tipografia, usePaleta, type Paleta } from "../tema";
|
|
10
10
|
import { Text } from "../texto";
|
|
11
|
+
import { type Idioma, type Textos, useIdiomaDeLaLibreria, useTextos } from "../idioma";
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Un movimiento de la bitácora de un registro, como lo responde cualquier endpoint `historial/`
|
|
@@ -64,20 +65,20 @@ const partirTipo = (tipo: string) => {
|
|
|
64
65
|
};
|
|
65
66
|
|
|
66
67
|
/** El valor de un campo tal como viene de la bitácora, listo para pintarse. */
|
|
67
|
-
const formatear = (valor: unknown): string => {
|
|
68
|
+
const formatear = (valor: unknown, t: Textos): string => {
|
|
68
69
|
if (valor === null || valor === undefined || valor === "") return "—";
|
|
69
|
-
if (typeof valor === "boolean") return valor ?
|
|
70
|
+
if (typeof valor === "boolean") return valor ? t.comun.si : t.comun.no;
|
|
70
71
|
if (Array.isArray(valor)) return valor.length ? valor.join(", ") : "—";
|
|
71
72
|
if (typeof valor === "object") return JSON.stringify(valor);
|
|
72
73
|
return String(valor);
|
|
73
74
|
};
|
|
74
75
|
|
|
75
76
|
/** La fecha y hora del movimiento, en la hora del teléfono. */
|
|
76
|
-
const fechaHora = (iso: string) => {
|
|
77
|
+
const fechaHora = (iso: string, idioma: Idioma) => {
|
|
77
78
|
const fecha = new Date(iso);
|
|
78
79
|
return Number.isNaN(fecha.getTime())
|
|
79
80
|
? "—"
|
|
80
|
-
: fecha.toLocaleString("es-MX", {
|
|
81
|
+
: fecha.toLocaleString(idioma === "en" ? "en-US" : "es-MX", {
|
|
81
82
|
day: "2-digit",
|
|
82
83
|
month: "short",
|
|
83
84
|
year: "numeric",
|
|
@@ -102,6 +103,8 @@ export function TarjetaDeEvento({
|
|
|
102
103
|
}) {
|
|
103
104
|
const c = usePaleta();
|
|
104
105
|
const styles = useStyles();
|
|
106
|
+
const t = useTextos();
|
|
107
|
+
const idioma = useIdiomaDeLaLibreria();
|
|
105
108
|
const [abierto, setAbierto] = React.useState(false);
|
|
106
109
|
|
|
107
110
|
const { accion, modelo } = partirTipo(evento.history_type);
|
|
@@ -121,16 +124,18 @@ export function TarjetaDeEvento({
|
|
|
121
124
|
<View style={styles.flex}>
|
|
122
125
|
<Text style={styles.titulo}>{modelo}</Text>
|
|
123
126
|
<Text style={styles.linea}>
|
|
124
|
-
{[accion, evento.history_user ||
|
|
127
|
+
{[accion, evento.history_user || t.historial.sistema]
|
|
128
|
+
.filter(Boolean)
|
|
129
|
+
.join(" · ")}
|
|
125
130
|
</Text>
|
|
126
|
-
<Text style={styles.linea}>{fechaHora(evento.history_date)}</Text>
|
|
131
|
+
<Text style={styles.linea}>{fechaHora(evento.history_date, idioma)}</Text>
|
|
127
132
|
</View>
|
|
128
133
|
</View>
|
|
129
134
|
|
|
130
135
|
{/* Un arreglo vacío es «se guardó sin mover nada»; nulo es la creación, que no lista
|
|
131
136
|
cambios. */}
|
|
132
137
|
{!abierto && cambiados?.length === 0 ? (
|
|
133
|
-
<Text style={styles.sinCambios}>
|
|
138
|
+
<Text style={styles.sinCambios}>{t.historial.sinCambios}</Text>
|
|
134
139
|
) : null}
|
|
135
140
|
|
|
136
141
|
{visibles.map((campo) => {
|
|
@@ -140,7 +145,7 @@ export function TarjetaDeEvento({
|
|
|
140
145
|
<Text style={[styles.etiqueta, cambiado && styles.etiquetaCambiada]}>
|
|
141
146
|
{labels[campo]}
|
|
142
147
|
</Text>
|
|
143
|
-
<Text style={styles.valor}>{formatear(evento[campo])}</Text>
|
|
148
|
+
<Text style={styles.valor}>{formatear(evento[campo], t)}</Text>
|
|
144
149
|
</View>
|
|
145
150
|
);
|
|
146
151
|
})}
|
|
@@ -154,10 +159,10 @@ export function TarjetaDeEvento({
|
|
|
154
159
|
>
|
|
155
160
|
<Text style={styles.alternarTexto}>
|
|
156
161
|
{abierto
|
|
157
|
-
?
|
|
162
|
+
? t.historial.ocultarCampos
|
|
158
163
|
: cambiados?.length
|
|
159
|
-
?
|
|
160
|
-
:
|
|
164
|
+
? t.historial.verTodos
|
|
165
|
+
: t.historial.verCampos}
|
|
161
166
|
</Text>
|
|
162
167
|
<MaterialCommunityIcons
|
|
163
168
|
name={abierto ? "chevron-up" : "chevron-down"}
|
|
@@ -205,20 +210,21 @@ export default function PantallaDeHistorial({
|
|
|
205
210
|
const insets = useSafeAreaInsets();
|
|
206
211
|
const { data, isLoading, isFetching, isError, refetch } = consulta;
|
|
207
212
|
const eventos = data?.history ?? [];
|
|
213
|
+
const t = useTextos();
|
|
208
214
|
|
|
209
215
|
return (
|
|
210
216
|
<View style={styles.root}>
|
|
211
|
-
<CabeceraEmpujada eyebrow={eyebrow} titulo=
|
|
217
|
+
<CabeceraEmpujada eyebrow={eyebrow} titulo={t.historial.titulo} />
|
|
212
218
|
{/* El error sólo se queda con la pantalla cuando no hay nada que enseñar: un refresco
|
|
213
219
|
que falla no debe borrar el historial que ya se estaba leyendo. */}
|
|
214
220
|
{isError && !data ? (
|
|
215
221
|
<EstadoVacio
|
|
216
222
|
icono="cloud-off-outline"
|
|
217
|
-
titulo=
|
|
218
|
-
texto=
|
|
223
|
+
titulo={t.historial.noCargo}
|
|
224
|
+
texto={t.comun.revisaLaSenal}
|
|
219
225
|
>
|
|
220
226
|
<BotonGrande icono="refresh" modo="contorno" onPress={() => refetch()}>
|
|
221
|
-
|
|
227
|
+
{t.comun.reintentar}
|
|
222
228
|
</BotonGrande>
|
|
223
229
|
</EstadoVacio>
|
|
224
230
|
) : isLoading ? (
|
|
@@ -240,7 +246,7 @@ export default function PantallaDeHistorial({
|
|
|
240
246
|
}
|
|
241
247
|
>
|
|
242
248
|
{eventos.length === 0 ? (
|
|
243
|
-
<EstadoVacio icono="history" titulo=
|
|
249
|
+
<EstadoVacio icono="history" titulo={t.historial.sinMovimientos} />
|
|
244
250
|
) : (
|
|
245
251
|
eventos.map((evento, indice) => (
|
|
246
252
|
// El backend no da una llave por evento (mezcla modelos con ids que se
|
|
@@ -13,16 +13,17 @@ import {
|
|
|
13
13
|
type PreferenciaDeTema,
|
|
14
14
|
} from "../tema";
|
|
15
15
|
import { Text } from "../texto";
|
|
16
|
+
import { type Textos, useTextos } from "../idioma";
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* La apariencia: **igual que el teléfono** por omisión, o fija. Tres opciones y no un interruptor de
|
|
19
20
|
* «modo oscuro», como el del portal: con un interruptor, quien lo toca una vez ya no vuelve a seguir
|
|
20
21
|
* al teléfono.
|
|
21
22
|
*/
|
|
22
|
-
const
|
|
23
|
-
{ key: "sistema", label:
|
|
24
|
-
{ key: "claro", label:
|
|
25
|
-
{ key: "oscuro", label:
|
|
23
|
+
const aparienciasDe = (t: Textos): Pestana<PreferenciaDeTema>[] => [
|
|
24
|
+
{ key: "sistema", label: t.perfil.telefono, icono: "cellphone" },
|
|
25
|
+
{ key: "claro", label: t.perfil.claro, icono: "white-balance-sunny" },
|
|
26
|
+
{ key: "oscuro", label: t.perfil.oscuro, icono: "weather-night" },
|
|
26
27
|
];
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -52,10 +53,11 @@ export default function PantallaDePerfil({
|
|
|
52
53
|
const styles = useStyles();
|
|
53
54
|
const [tema, setTema] = usePreferenciaDeTema();
|
|
54
55
|
const insets = useSafeAreaInsets();
|
|
56
|
+
const t = useTextos();
|
|
55
57
|
|
|
56
58
|
return (
|
|
57
59
|
<View style={styles.root}>
|
|
58
|
-
<CabeceraEmpujada eyebrow={rotulo} titulo=
|
|
60
|
+
<CabeceraEmpujada eyebrow={rotulo} titulo={t.perfil.titulo} />
|
|
59
61
|
|
|
60
62
|
<ContenidoDePantalla paddingBottom={insets.bottom + 20}>
|
|
61
63
|
<ScrollView
|
|
@@ -66,13 +68,13 @@ export default function PantallaDePerfil({
|
|
|
66
68
|
{/* Paper recorta título y subtítulo a un renglón por omisión: un nombre
|
|
67
69
|
largo con la letra al 180% se perdía. */}
|
|
68
70
|
<Card.Title
|
|
69
|
-
title={nombre ||
|
|
71
|
+
title={nombre || t.comun.noDisponible}
|
|
70
72
|
titleVariant={"titleLarge"}
|
|
71
73
|
titleStyle={{ fontWeight: "bold" }}
|
|
72
74
|
titleNumberOfLines={0}
|
|
73
75
|
subtitleNumberOfLines={0}
|
|
74
76
|
subtitleStyle={renglon(tipografia.secundario)}
|
|
75
|
-
subtitle={subtitulo ||
|
|
77
|
+
subtitle={subtitulo || t.comun.noDisponible}
|
|
76
78
|
subtitleVariant={"bodySmall"}
|
|
77
79
|
left={(props) => (
|
|
78
80
|
<Avatar.Text
|
|
@@ -85,8 +87,8 @@ export default function PantallaDePerfil({
|
|
|
85
87
|
</Card>
|
|
86
88
|
|
|
87
89
|
<View style={styles.apariencia}>
|
|
88
|
-
<Text style={styles.rotulo}>
|
|
89
|
-
<Pestanas pestanas={
|
|
90
|
+
<Text style={styles.rotulo}>{t.perfil.apariencia}</Text>
|
|
91
|
+
<Pestanas pestanas={aparienciasDe(t)} activa={tema} onSelect={setTema} />
|
|
90
92
|
</View>
|
|
91
93
|
|
|
92
94
|
{/* El tope de ancho vive en este View y no en el Button: el Surface de
|
|
@@ -100,7 +102,7 @@ export default function PantallaDePerfil({
|
|
|
100
102
|
loading={saliendo}
|
|
101
103
|
disabled={saliendo}
|
|
102
104
|
>
|
|
103
|
-
|
|
105
|
+
{t.perfil.cerrarSesion}
|
|
104
106
|
</Button>
|
|
105
107
|
</View>
|
|
106
108
|
{children}
|