fix/transportes-traduccion-erronea
This commit is contained in:
@@ -259,7 +259,29 @@
|
||||
"transporters": "Carriers",
|
||||
"drivers": "Drivers",
|
||||
"trailers": "Trailers",
|
||||
"vehicles": "Vehicles"
|
||||
"vehicles": "Vehicles",
|
||||
"vehicle_transport_types": {
|
||||
"ar": "Armored Truck",
|
||||
"au": "Automobiles",
|
||||
"bt": "Box Truck",
|
||||
"bu": "Bus",
|
||||
"bv": "Beverage Truck (Refer or not)",
|
||||
"by": "Bicycle",
|
||||
"co": "Construction Vehicle (general)",
|
||||
"ev": "Emergency Vehicle (general)",
|
||||
"fe": "Ferry",
|
||||
"fm": "Farm Tractor",
|
||||
"gb": "Garbage Truck",
|
||||
"mc": "Motorcycle",
|
||||
"oc": "Other",
|
||||
"pm": "Pick-up Truck w/camper",
|
||||
"pn": "Panel Truck",
|
||||
"pu": "Pickup Truck",
|
||||
"pv": "Passenger",
|
||||
"rv": "Recreation Vehicle (RV)",
|
||||
"tr": "Semi Tracker",
|
||||
"tv": "Van"
|
||||
}
|
||||
},
|
||||
"reports": {
|
||||
"title": "Reports",
|
||||
|
||||
@@ -258,7 +258,29 @@
|
||||
"transporters": "Transportistas",
|
||||
"drivers": "Conductores",
|
||||
"trailers": "Trailers",
|
||||
"vehicles": "Vehículos"
|
||||
"vehicles": "Vehículos",
|
||||
"vehicle_transport_types": {
|
||||
"ar": "Camión Blindado",
|
||||
"au": "Automóviles",
|
||||
"bt": "Camión de Caja",
|
||||
"bu": "Autobús",
|
||||
"bv": "Camión de Bebidas",
|
||||
"by": "Bicicleta",
|
||||
"co": "Vehículo de Construcción",
|
||||
"ev": "Vehículo de Emergencia",
|
||||
"fe": "Ferry",
|
||||
"fm": "Tractor Agrícola",
|
||||
"gb": "Camión de Basura",
|
||||
"mc": "Motocicleta",
|
||||
"oc": "Otro",
|
||||
"pm": "Camioneta con cabina",
|
||||
"pn": "Camión Panel",
|
||||
"pu": "Camioneta (Pick-up)",
|
||||
"pv": "Pasajero",
|
||||
"rv": "Vehículo Recreativo (RV)",
|
||||
"tr": "Tractocamión",
|
||||
"tv": "Van"
|
||||
}
|
||||
},
|
||||
"reports": {
|
||||
"title": "Reportes",
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { Vehicle } from '$lib/api/dashboard/a76/vehicles';
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
import { tv, tvTransportType } from '$lib/i18n/vehicles';
|
||||
|
||||
export function createColumns(
|
||||
onSuccess: () => void,
|
||||
@@ -13,49 +14,50 @@ export function createColumns(
|
||||
return [
|
||||
{
|
||||
accessorKey: 'vehicle_key',
|
||||
header: 'Clave',
|
||||
header: tv('col_key'),
|
||||
cell: ({ row }) => {
|
||||
return row.original.vehicle_key;
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'brand',
|
||||
header: 'Marca',
|
||||
header: tv('col_brand'),
|
||||
cell: ({ row }) => {
|
||||
return row.original.brand || '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'year',
|
||||
header: 'Año',
|
||||
header: tv('col_year'),
|
||||
cell: ({ row }) => {
|
||||
return row.original.year || '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'plate_number',
|
||||
header: 'Placas',
|
||||
header: tv('col_plate'),
|
||||
cell: ({ row }) => {
|
||||
return row.original.plate_number || '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'transporter_key',
|
||||
header: 'Transportista',
|
||||
header: tv('col_transporter'),
|
||||
cell: ({ row }) => {
|
||||
return row.original.transporter_key || '-';
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'transport_type',
|
||||
header: 'Tipo Transporte',
|
||||
header: tv('col_transport_type'),
|
||||
cell: ({ row }) => {
|
||||
return row.original.transport_type || '-';
|
||||
if (!row.original.transport_type) return '-';
|
||||
return `${row.original.transport_type} - ${tvTransportType(row.original.transport_type, row.original.transport_type)}`;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
header: tv('col_actions'),
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import { countriesApi, type Country } from '$lib/api/dashboard/reference_data/countries';
|
||||
import { statesApi, type State } from '$lib/api/dashboard/reference_data/states';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { tv, tvTransportType } from '$lib/i18n/vehicles';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
let {
|
||||
@@ -25,7 +26,7 @@
|
||||
|
||||
// Determinar si es modo edición o creación
|
||||
const isEdit = $derived(!!item);
|
||||
const title = $derived(isEdit ? 'Editar Vehículo' : 'Nuevo Vehículo');
|
||||
const title = $derived(isEdit ? tv('dialog_edit_title') : tv('dialog_new_title'));
|
||||
|
||||
// Estado del formulario
|
||||
let formData = $state<Vehicle>({
|
||||
@@ -66,35 +67,16 @@
|
||||
let refsLoading = $state(false);
|
||||
|
||||
const ENTITY_OPTS = [
|
||||
{ value: '', label: '— Sin código —' },
|
||||
{ value: '', label: tv('no_code') },
|
||||
{ value: 'C', label: 'C' },
|
||||
{ value: 'I', label: 'I' },
|
||||
{ value: 'A', label: 'A' },
|
||||
{ value: 'B', label: 'B' }
|
||||
];
|
||||
|
||||
const TRANSPORT_TYPE_TRANSLATIONS: Record<string, string> = {
|
||||
AR: 'Camión Blindado',
|
||||
AU: 'Automóviles',
|
||||
BT: 'Camión de Caja',
|
||||
BU: 'Autobús',
|
||||
BV: 'Camión de Bebidas',
|
||||
BY: 'Bicicleta',
|
||||
CO: 'Vehículo de Construcción',
|
||||
EV: 'Vehículo de Emergencia',
|
||||
FE: 'Ferry',
|
||||
FM: 'Tractor Agrícola',
|
||||
GB: 'Camión de Basura',
|
||||
MC: 'Motocicleta',
|
||||
OC: 'Otro',
|
||||
PM: 'Camioneta con cabina',
|
||||
PN: 'Camión Panel',
|
||||
PU: 'Camioneta (Pick-up)',
|
||||
PV: 'Pasajero',
|
||||
RV: 'Vehículo Recreativo (RV)',
|
||||
TR: 'Tractocamión',
|
||||
TV: 'Van'
|
||||
};
|
||||
function getTransportTypeText(transportCode?: string, description?: string): string {
|
||||
return tvTransportType(transportCode, description);
|
||||
}
|
||||
|
||||
const countryM3 = $derived(
|
||||
countries.find((c) => c.ame_key === formData.country)?.m3_key ?? null
|
||||
@@ -110,7 +92,7 @@
|
||||
try {
|
||||
const [tr, tt, cc, ss] = await Promise.all([
|
||||
transportersApi.list(cid, { page: 1, page_size: 100 }),
|
||||
transportTypesApi.list(1, 100),
|
||||
transportTypesApi.list(cid, 1, 100),
|
||||
countriesApi.list(1, 100),
|
||||
statesApi.list(1, 100)
|
||||
]);
|
||||
@@ -181,12 +163,12 @@
|
||||
try {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
throw new Error('No hay una compañía seleccionada');
|
||||
throw new Error(tv('no_company_selected'));
|
||||
}
|
||||
|
||||
// Validación básica
|
||||
if (!formData.vehicle_key.trim()) {
|
||||
throw new Error('La clave del vehículo es requerida');
|
||||
throw new Error(tv('vehicle_key_required'));
|
||||
}
|
||||
|
||||
let response;
|
||||
@@ -210,7 +192,7 @@
|
||||
onSuccess();
|
||||
}
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar el vehículo';
|
||||
error = e instanceof Error ? e.message : tv('save_error');
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
@@ -238,8 +220,8 @@
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
{isEdit
|
||||
? 'Modifica los datos del vehículo'
|
||||
: 'Completa los datos para crear un nuevo vehículo de transporte'}
|
||||
? tv('dialog_edit_description')
|
||||
: tv('dialog_new_description')}
|
||||
</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
|
||||
@@ -258,21 +240,21 @@
|
||||
|
||||
<Tabs.Root value="general" class="w-full">
|
||||
<Tabs.List class="grid w-full grid-cols-2">
|
||||
<Tabs.Trigger value="general">Información General</Tabs.Trigger>
|
||||
<Tabs.Trigger value="details">Seguro y Detalles</Tabs.Trigger>
|
||||
<Tabs.Trigger value="general">{tv('tab_general')}</Tabs.Trigger>
|
||||
<Tabs.Trigger value="details">{tv('tab_details')}</Tabs.Trigger>
|
||||
</Tabs.List>
|
||||
|
||||
<Tabs.Content value="general" class="mt-6 space-y-8">
|
||||
<!-- Información del Vehículo -->
|
||||
<section class="space-y-4">
|
||||
<h3 class="border-b pb-2 text-sm font-bold uppercase tracking-wider text-muted-foreground">
|
||||
Identificación del Vehículo
|
||||
{tv('section_vehicle_identification')}
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="grid gap-2 md:col-span-2">
|
||||
<Label for="vehicle_key"
|
||||
>Clave del Vehículo <span class="text-destructive">*</span></Label
|
||||
>{tv('label_vehicle_key')} <span class="text-destructive">*</span></Label
|
||||
>
|
||||
<Input
|
||||
id="vehicle_key"
|
||||
@@ -280,27 +262,27 @@
|
||||
disabled={isEdit}
|
||||
required
|
||||
maxlength={14}
|
||||
placeholder="Ej: VH001"
|
||||
placeholder={tv('placeholder_vehicle_key')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="brand">Marca</Label>
|
||||
<Input id="brand" bind:value={formData.brand} maxlength={20} placeholder="Ej: Kenworth" />
|
||||
<Label for="brand">{tv('label_brand')}</Label>
|
||||
<Input id="brand" bind:value={formData.brand} maxlength={20} placeholder={tv('placeholder_brand')} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="year">Año</Label>
|
||||
<Input id="year" bind:value={formData.year} maxlength={4} placeholder="YYYY" />
|
||||
<Label for="year">{tv('label_year')}</Label>
|
||||
<Input id="year" bind:value={formData.year} maxlength={4} placeholder={tv('placeholder_year')} />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="plate_number">Placas</Label>
|
||||
<Input id="plate_number" bind:value={formData.plate_number} maxlength={17} placeholder="Placas actuales" />
|
||||
<Label for="plate_number">{tv('label_plate_number')}</Label>
|
||||
<Input id="plate_number" bind:value={formData.plate_number} maxlength={17} placeholder={tv('placeholder_plate_number')} />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="series">Serie / VIN</Label>
|
||||
<Input id="series" bind:value={formData.series} maxlength={30} placeholder="Número de serie" />
|
||||
<Label for="series">{tv('label_series')}</Label>
|
||||
<Input id="series" bind:value={formData.series} maxlength={30} placeholder={tv('placeholder_series')} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -308,22 +290,22 @@
|
||||
<!-- Datos de Transporte -->
|
||||
<section class="space-y-4">
|
||||
<h3 class="border-b pb-2 text-sm font-bold uppercase tracking-wider text-muted-foreground">
|
||||
Datos de Transporte
|
||||
{tv('section_transport_data')}
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="grid gap-2 md:col-span-2">
|
||||
<Label for="transporter_key">Transportista</Label>
|
||||
<Label for="transporter_key">{tv('label_transporter')}</Label>
|
||||
<Select.Root type="single" bind:value={formData.transporter_key} disabled={refsLoading}>
|
||||
<Select.Trigger class="w-full">
|
||||
{refsLoading
|
||||
? '...'
|
||||
: formData.transporter_key
|
||||
? `${formData.transporter_key} — ${transporters.find((t) => t.transporter_key === formData.transporter_key)?.name ?? ''}`
|
||||
: '— Seleccionar transportista —'}
|
||||
: tv('select_transporter')}
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-60">
|
||||
<Select.Item value="" label="Vacío">— Vacío —</Select.Item>
|
||||
<Select.Item value="" label={tv('empty')}>{tv('empty')}</Select.Item>
|
||||
{#each transporters as t}
|
||||
<Select.Item value={t.transporter_key} label={t.transporter_key}>
|
||||
{t.transporter_key} — {t.name || t.short_name || ''}
|
||||
@@ -334,30 +316,30 @@
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="transport_identifier">ID Transporte</Label>
|
||||
<Label for="transport_identifier">{tv('label_transport_identifier')}</Label>
|
||||
<Input
|
||||
id="transport_identifier"
|
||||
bind:value={formData.transport_identifier}
|
||||
maxlength={30}
|
||||
placeholder="Identificador único"
|
||||
placeholder={tv('placeholder_transport_identifier')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="transport_type">Tipo de Transporte</Label>
|
||||
<Label for="transport_type">{tv('label_transport_type')}</Label>
|
||||
<Select.Root type="single" bind:value={formData.transport_type} disabled={refsLoading}>
|
||||
<Select.Trigger class="w-full" id="transport_type">
|
||||
{refsLoading
|
||||
? '...'
|
||||
: formData.transport_type
|
||||
? `${formData.transport_type} — ${TRANSPORT_TYPE_TRANSLATIONS[formData.transport_type] || transportTypes.find((x) => x.transport_code === formData.transport_type)?.description || ''}`
|
||||
: '— Opcional —'}
|
||||
? `${formData.transport_type} — ${getTransportTypeText(formData.transport_type, transportTypes.find((x) => (x.transport_code || '').trim().toUpperCase() === (formData.transport_type || '').trim().toUpperCase())?.description)}`
|
||||
: tv('optional')}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="" label="Vacío">— Vacío —</Select.Item>
|
||||
<Select.Item value="" label={tv('empty')}>{tv('empty')}</Select.Item>
|
||||
{#each transportTypes as x}
|
||||
<Select.Item value={x.transport_code} label={x.transport_code}>
|
||||
{x.transport_code} — {TRANSPORT_TYPE_TRANSLATIONS[x.transport_code] || x.description}
|
||||
{x.transport_code} — {getTransportTypeText(x.transport_code, x.description)}
|
||||
</Select.Item>
|
||||
{/each}
|
||||
</Select.Content>
|
||||
@@ -365,11 +347,11 @@
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="entity_code">Código de Entidad</Label>
|
||||
<Label for="entity_code">{tv('label_entity_code')}</Label>
|
||||
<Select.Root type="single" bind:value={formData.entity_code}>
|
||||
<Select.Trigger class="w-full" id="entity_code">
|
||||
{ENTITY_OPTS.find((o) => o.value === (formData.entity_code || ''))?.label ??
|
||||
'— Sin código —'}
|
||||
tv('no_code')}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
{#each ENTITY_OPTS as o}
|
||||
@@ -377,12 +359,12 @@
|
||||
{/each}
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
<p class="text-[10px] text-muted-foreground uppercase">Obligatorio al crear (reglas CSV)</p>
|
||||
<p class="text-[10px] text-muted-foreground uppercase">{tv('entity_required_hint')}</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="sct_permission">Permiso SCT</Label>
|
||||
<Input id="sct_permission" bind:value={formData.sct_permission} maxlength={40} placeholder="Número de permiso" />
|
||||
<Label for="sct_permission">{tv('label_sct_permission')}</Label>
|
||||
<Input id="sct_permission" bind:value={formData.sct_permission} maxlength={40} placeholder={tv('placeholder_sct_permission')} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -392,31 +374,31 @@
|
||||
<!-- Seguro y Otros -->
|
||||
<section class="space-y-4">
|
||||
<h3 class="border-b pb-2 text-sm font-bold uppercase tracking-wider text-muted-foreground">
|
||||
Seguro y Otros
|
||||
{tv('section_insurance')}
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="grid gap-2 md:col-span-2">
|
||||
<Label for="insurance_company_name">Aseguradora</Label>
|
||||
<Label for="insurance_company_name">{tv('label_insurance_company')}</Label>
|
||||
<Input
|
||||
id="insurance_company_name"
|
||||
bind:value={formData.insurance_company_name}
|
||||
maxlength={30}
|
||||
placeholder="Nombre de la compañía"
|
||||
placeholder={tv('placeholder_insurance_company')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="insurance_number">Póliza</Label>
|
||||
<Label for="insurance_number">{tv('label_insurance_number')}</Label>
|
||||
<Input
|
||||
id="insurance_number"
|
||||
bind:value={formData.insurance_number}
|
||||
maxlength={20}
|
||||
placeholder="Número de póliza"
|
||||
placeholder={tv('placeholder_insurance_number')}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="insurance_amount">Monto</Label>
|
||||
<Label for="insurance_amount">{tv('label_insurance_amount')}</Label>
|
||||
<Input
|
||||
id="insurance_amount"
|
||||
type="number"
|
||||
@@ -426,7 +408,7 @@
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="dot_number">Número DOT</Label>
|
||||
<Label for="dot_number">{tv('label_dot_number')}</Label>
|
||||
<Input id="dot_number" bind:value={formData.dot_number} maxlength={8} placeholder="DOT ID" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -435,22 +417,22 @@
|
||||
<!-- Ubicación y Detalles -->
|
||||
<section class="space-y-4">
|
||||
<h3 class="border-b pb-2 text-sm font-bold uppercase tracking-wider text-muted-foreground">
|
||||
Ubicación y Detalles
|
||||
{tv('section_location')}
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<div class="grid gap-2">
|
||||
<Label for="country">País (AME)</Label>
|
||||
<Label for="country">{tv('label_country')}</Label>
|
||||
<Select.Root type="single" bind:value={formData.country} disabled={refsLoading}>
|
||||
<Select.Trigger class="w-full" id="country">
|
||||
{refsLoading
|
||||
? '...'
|
||||
: formData.country
|
||||
? `${formData.country} — ${countries.find((c) => c.ame_key === formData.country)?.description_es ?? ''}`
|
||||
: '— Opcional —'}
|
||||
: tv('optional')}
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-60">
|
||||
<Select.Item value="" label="Vacío">— Vacío —</Select.Item>
|
||||
<Select.Item value="" label={tv('empty')}>{tv('empty')}</Select.Item>
|
||||
{#each countries as c}
|
||||
<Select.Item value={c.ame_key} label={c.ame_key}>
|
||||
{c.ame_key} — {c.description_es}
|
||||
@@ -460,16 +442,16 @@
|
||||
</Select.Root>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="state">Estado</Label>
|
||||
<Label for="state">{tv('label_state')}</Label>
|
||||
<Select.Root type="single" bind:value={formData.state} disabled={refsLoading}>
|
||||
<Select.Trigger class="w-full" id="state">
|
||||
{refsLoading
|
||||
? '...'
|
||||
: formData.state ||
|
||||
(formData.country ? '— Selecciona —' : '— Primero el país —')}
|
||||
(formData.country ? tv('select_state') : tv('select_country_first'))}
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-60">
|
||||
<Select.Item value="" label="Vacío">— Vacío —</Select.Item>
|
||||
<Select.Item value="" label={tv('empty')}>{tv('empty')}</Select.Item>
|
||||
{#each statesFiltered as s}
|
||||
<Select.Item value={s.description} label={s.description}>
|
||||
{s.description}
|
||||
@@ -480,21 +462,21 @@
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="city">Ciudad</Label>
|
||||
<Input id="city" bind:value={formData.city} maxlength={30} placeholder="Ciudad/Localidad" />
|
||||
<Label for="city">{tv('label_city')}</Label>
|
||||
<Input id="city" bind:value={formData.city} maxlength={30} placeholder={tv('placeholder_city')} />
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="color">Color</Label>
|
||||
<Input id="color" bind:value={formData.color} maxlength={20} placeholder="Color del vehículo" />
|
||||
<Label for="color">{tv('label_color')}</Label>
|
||||
<Input id="color" bind:value={formData.color} maxlength={20} placeholder={tv('placeholder_color')} />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="container_key">Tipo Contenedor</Label>
|
||||
<Label for="container_key">{tv('label_container_type')}</Label>
|
||||
<Input
|
||||
id="container_key"
|
||||
bind:value={formData.container_key}
|
||||
maxlength={3}
|
||||
placeholder="Ej: 40G"
|
||||
placeholder={tv('placeholder_container_type')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -502,12 +484,12 @@
|
||||
|
||||
<section class="space-y-4 pt-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="description">Descripción Adicional</Label>
|
||||
<Label for="description">{tv('label_additional_description')}</Label>
|
||||
<Input
|
||||
id="description"
|
||||
bind:value={formData.description}
|
||||
maxlength={100}
|
||||
placeholder="Notas adicionales sobre el vehículo..."
|
||||
placeholder={tv('placeholder_additional_description')}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
@@ -516,10 +498,10 @@
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={handleCancel} disabled={loading}>
|
||||
Cancelar
|
||||
{tv('cancel')}
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
|
||||
{loading ? tv('saving') : isEdit ? tv('update') : tv('create')}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from './create-edit-dialog.svelte';
|
||||
import { tv } from '$lib/i18n/vehicles';
|
||||
|
||||
let {
|
||||
item,
|
||||
@@ -26,14 +27,14 @@
|
||||
async function handleDelete() {
|
||||
if (
|
||||
!confirm(
|
||||
`¿Estás seguro de eliminar el vehículo "${item.vehicle_key}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`
|
||||
tv('delete_confirm', { key: item.vehicle_key })
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
alert('❌ Error: No hay una compañía seleccionada');
|
||||
alert(tv('no_company_error'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -45,25 +46,25 @@
|
||||
|
||||
if (response.error) {
|
||||
if (response.status === 401) {
|
||||
error = 'Sesión expirada. Recargando página...';
|
||||
error = tv('session_expired');
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1500);
|
||||
} else {
|
||||
error = response.error;
|
||||
alert(`❌ Error al eliminar:\n\n${response.error}`);
|
||||
alert(`${tv('delete_error_title')}\n\n${response.error}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Éxito
|
||||
alert(`✅ Vehículo "${item.vehicle_key}" eliminado correctamente`);
|
||||
alert(tv('delete_success_item', { key: item.vehicle_key }));
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al eliminar';
|
||||
alert(`❌ Error: ${error}`);
|
||||
error = e instanceof Error ? e.message : tv('delete_generic_error');
|
||||
alert(tv('delete_error_alert', { error }));
|
||||
console.error('Error deleting:', e);
|
||||
} finally {
|
||||
loading = false;
|
||||
@@ -88,18 +89,18 @@
|
||||
<DropdownMenu.Trigger disabled={!canEdit && !canDelete}>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<span class="sr-only">{tv('menu_open')}</span>
|
||||
<EllipsisVertical size={16} />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end" class="w-[160px]">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Label>{tv('menu_actions')}</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
{#if canEdit}
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
{tv('edit')}
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
{#if canDelete}
|
||||
@@ -110,7 +111,7 @@
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
{tv('delete')}
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.Content>
|
||||
|
||||
266
frontend/src/lib/i18n/vehicles.ts
Normal file
266
frontend/src/lib/i18n/vehicles.ts
Normal file
@@ -0,0 +1,266 @@
|
||||
import { getLocale } from '$lib/paraglide/runtime';
|
||||
|
||||
type VehiclesLocale = 'es' | 'en';
|
||||
|
||||
const MESSAGES = {
|
||||
es: {
|
||||
shortcuts_scope: 'Vehículos (transporte)',
|
||||
shortcuts_save: 'Guardar',
|
||||
page_title: 'Vehículos (Transporte)',
|
||||
page_subtitle: 'Gestión del catálogo de camiones y vehículos de transporte',
|
||||
new_vehicle: 'Nuevo Vehículo',
|
||||
vehicle_list: 'Listado de Vehículos',
|
||||
search_key: 'Clave',
|
||||
search_plate: 'Placas',
|
||||
loading_vehicles: 'Cargando vehículos...',
|
||||
showing_records: 'Mostrando {shown} de {total} registros',
|
||||
edit: 'Editar',
|
||||
delete: 'Eliminar',
|
||||
delete_confirm: '¿Estás seguro de eliminar el vehículo "{key}"?\n\nNota: No se puede eliminar si tiene registros relacionados.',
|
||||
delete_error_title: '❌ Error al eliminar:',
|
||||
delete_success: '✅ Vehículo eliminado correctamente',
|
||||
col_key: 'Clave',
|
||||
col_brand: 'Marca',
|
||||
col_year: 'Año',
|
||||
col_plate: 'Placas',
|
||||
col_transporter: 'Transportista',
|
||||
col_transport_type: 'Tipo Transporte',
|
||||
col_actions: 'Acciones',
|
||||
menu_open: 'Abrir menú',
|
||||
menu_actions: 'Acciones',
|
||||
session_expired: 'Sesión expirada. Recargando página...',
|
||||
no_company_error: '❌ Error: No hay una compañía seleccionada',
|
||||
delete_generic_error: 'Error al eliminar',
|
||||
delete_error_alert: '❌ Error: {error}',
|
||||
delete_success_item: '✅ Vehículo "{key}" eliminado correctamente',
|
||||
dialog_edit_title: 'Editar Vehículo',
|
||||
dialog_new_title: 'Nuevo Vehículo',
|
||||
dialog_edit_description: 'Modifica los datos del vehículo',
|
||||
dialog_new_description: 'Completa los datos para crear un nuevo vehículo de transporte',
|
||||
tab_general: 'Información General',
|
||||
tab_details: 'Seguro y Detalles',
|
||||
section_vehicle_identification: 'Identificación del Vehículo',
|
||||
label_vehicle_key: 'Clave del Vehículo',
|
||||
placeholder_vehicle_key: 'Ej: VH001',
|
||||
label_brand: 'Marca',
|
||||
placeholder_brand: 'Ej: Kenworth',
|
||||
label_year: 'Año',
|
||||
placeholder_year: 'YYYY',
|
||||
label_plate_number: 'Placas',
|
||||
placeholder_plate_number: 'Placas actuales',
|
||||
label_series: 'Serie / VIN',
|
||||
placeholder_series: 'Número de serie',
|
||||
section_transport_data: 'Datos de Transporte',
|
||||
label_transporter: 'Transportista',
|
||||
select_transporter: '— Seleccionar transportista —',
|
||||
empty: '— Vacío —',
|
||||
label_transport_identifier: 'ID Transporte',
|
||||
placeholder_transport_identifier: 'Identificador único',
|
||||
label_transport_type: 'Tipo de Transporte',
|
||||
optional: '— Opcional —',
|
||||
label_entity_code: 'Código de Entidad',
|
||||
no_code: '— Sin código —',
|
||||
entity_required_hint: 'Obligatorio al crear (reglas CSV)',
|
||||
label_sct_permission: 'Permiso SCT',
|
||||
placeholder_sct_permission: 'Número de permiso',
|
||||
section_insurance: 'Seguro y Otros',
|
||||
label_insurance_company: 'Aseguradora',
|
||||
placeholder_insurance_company: 'Nombre de la compañía',
|
||||
label_insurance_number: 'Póliza',
|
||||
placeholder_insurance_number: 'Número de póliza',
|
||||
label_insurance_amount: 'Monto',
|
||||
label_dot_number: 'Número DOT',
|
||||
label_country: 'País (AME)',
|
||||
label_state: 'Estado',
|
||||
select_state: '— Selecciona —',
|
||||
select_country_first: '— Primero el país —',
|
||||
label_city: 'Ciudad',
|
||||
placeholder_city: 'Ciudad/Localidad',
|
||||
label_color: 'Color',
|
||||
placeholder_color: 'Color del vehículo',
|
||||
label_container_type: 'Tipo Contenedor',
|
||||
placeholder_container_type: 'Ej: 40G',
|
||||
section_location: 'Ubicación y Detalles',
|
||||
label_additional_description: 'Descripción Adicional',
|
||||
placeholder_additional_description: 'Notas adicionales sobre el vehículo...',
|
||||
cancel: 'Cancelar',
|
||||
saving: 'Guardando...',
|
||||
update: 'Actualizar',
|
||||
create: 'Crear',
|
||||
no_company_selected: 'No hay una compañía seleccionada',
|
||||
vehicle_key_required: 'La clave del vehículo es requerida',
|
||||
save_error: 'Error al guardar el vehículo',
|
||||
transport_types: {
|
||||
AR: 'Camión Blindado',
|
||||
AU: 'Automóviles',
|
||||
BT: 'Camión de Caja',
|
||||
BU: 'Autobús',
|
||||
BV: 'Camión de Bebidas',
|
||||
BY: 'Bicicleta',
|
||||
CO: 'Vehículo de Construcción',
|
||||
EV: 'Vehículo de Emergencia',
|
||||
FE: 'Ferry',
|
||||
FM: 'Tractor Agrícola',
|
||||
GB: 'Camión de Basura',
|
||||
MC: 'Motocicleta',
|
||||
OC: 'Otro',
|
||||
PM: 'Camioneta con cabina',
|
||||
PN: 'Camión Panel',
|
||||
PU: 'Camioneta (Pick-up)',
|
||||
PV: 'Pasajero',
|
||||
RV: 'Vehículo Recreativo (RV)',
|
||||
TR: 'Tractocamión',
|
||||
TV: 'Van'
|
||||
}
|
||||
},
|
||||
en: {
|
||||
shortcuts_scope: 'Vehicles (transport)',
|
||||
shortcuts_save: 'Save',
|
||||
page_title: 'Vehicles (Transport)',
|
||||
page_subtitle: 'Transport trucks and vehicles catalog management',
|
||||
new_vehicle: 'New Vehicle',
|
||||
vehicle_list: 'Vehicle List',
|
||||
search_key: 'Key',
|
||||
search_plate: 'Plates',
|
||||
loading_vehicles: 'Loading vehicles...',
|
||||
showing_records: 'Showing {shown} of {total} records',
|
||||
edit: 'Edit',
|
||||
delete: 'Delete',
|
||||
delete_confirm: 'Are you sure you want to delete vehicle "{key}"?\n\nNote: It cannot be deleted if it has related records.',
|
||||
delete_error_title: '❌ Error deleting:',
|
||||
delete_success: '✅ Vehicle deleted successfully',
|
||||
col_key: 'Key',
|
||||
col_brand: 'Brand',
|
||||
col_year: 'Year',
|
||||
col_plate: 'Plates',
|
||||
col_transporter: 'Transporter',
|
||||
col_transport_type: 'Transport Type',
|
||||
col_actions: 'Actions',
|
||||
menu_open: 'Open menu',
|
||||
menu_actions: 'Actions',
|
||||
session_expired: 'Session expired. Reloading page...',
|
||||
no_company_error: '❌ Error: No company selected',
|
||||
delete_generic_error: 'Error deleting',
|
||||
delete_error_alert: '❌ Error: {error}',
|
||||
delete_success_item: '✅ Vehicle "{key}" deleted successfully',
|
||||
dialog_edit_title: 'Edit Vehicle',
|
||||
dialog_new_title: 'New Vehicle',
|
||||
dialog_edit_description: 'Modify vehicle data',
|
||||
dialog_new_description: 'Complete the data to create a new transport vehicle',
|
||||
tab_general: 'General Information',
|
||||
tab_details: 'Insurance and Details',
|
||||
section_vehicle_identification: 'Vehicle Identification',
|
||||
label_vehicle_key: 'Vehicle Key',
|
||||
placeholder_vehicle_key: 'Eg: VH001',
|
||||
label_brand: 'Brand',
|
||||
placeholder_brand: 'Eg: Kenworth',
|
||||
label_year: 'Year',
|
||||
placeholder_year: 'YYYY',
|
||||
label_plate_number: 'Plates',
|
||||
placeholder_plate_number: 'Current plates',
|
||||
label_series: 'Series / VIN',
|
||||
placeholder_series: 'Serial number',
|
||||
section_transport_data: 'Transport Data',
|
||||
label_transporter: 'Transporter',
|
||||
select_transporter: '— Select transporter —',
|
||||
empty: '— Empty —',
|
||||
label_transport_identifier: 'Transport ID',
|
||||
placeholder_transport_identifier: 'Unique identifier',
|
||||
label_transport_type: 'Transport Type',
|
||||
optional: '— Optional —',
|
||||
label_entity_code: 'Entity Code',
|
||||
no_code: '— No code —',
|
||||
entity_required_hint: 'Required on create (CSV rules)',
|
||||
label_sct_permission: 'SCT Permit',
|
||||
placeholder_sct_permission: 'Permit number',
|
||||
section_insurance: 'Insurance and Others',
|
||||
label_insurance_company: 'Insurance Company',
|
||||
placeholder_insurance_company: 'Company name',
|
||||
label_insurance_number: 'Policy',
|
||||
placeholder_insurance_number: 'Policy number',
|
||||
label_insurance_amount: 'Amount',
|
||||
label_dot_number: 'DOT Number',
|
||||
label_country: 'Country (AME)',
|
||||
label_state: 'State',
|
||||
select_state: '— Select —',
|
||||
select_country_first: '— Select country first —',
|
||||
label_city: 'City',
|
||||
placeholder_city: 'City/Location',
|
||||
label_color: 'Color',
|
||||
placeholder_color: 'Vehicle color',
|
||||
label_container_type: 'Container Type',
|
||||
placeholder_container_type: 'Eg: 40G',
|
||||
section_location: 'Location and Details',
|
||||
label_additional_description: 'Additional Description',
|
||||
placeholder_additional_description: 'Additional notes about the vehicle...',
|
||||
cancel: 'Cancel',
|
||||
saving: 'Saving...',
|
||||
update: 'Update',
|
||||
create: 'Create',
|
||||
no_company_selected: 'No company selected',
|
||||
vehicle_key_required: 'Vehicle key is required',
|
||||
save_error: 'Error saving vehicle',
|
||||
transport_types: {
|
||||
AR: 'Armored Truck',
|
||||
AU: 'Automobiles',
|
||||
BT: 'Box Truck',
|
||||
BU: 'Bus',
|
||||
BV: 'Beverage Truck (Refer or not)',
|
||||
BY: 'Bicycle',
|
||||
CO: 'Construction Vehicle (general)',
|
||||
EV: 'Emergency Vehicle (general)',
|
||||
FE: 'Ferry',
|
||||
FM: 'Farm Tractor',
|
||||
GB: 'Garbage Truck',
|
||||
MC: 'Motorcycle',
|
||||
OC: 'Other',
|
||||
PM: 'Pick-up Truck w/camper',
|
||||
PN: 'Panel Truck',
|
||||
PU: 'Pickup Truck',
|
||||
PV: 'Passenger',
|
||||
RV: 'Recreation Vehicle (RV)',
|
||||
TR: 'Semi Tracker',
|
||||
TV: 'Van'
|
||||
}
|
||||
}
|
||||
} as const;
|
||||
|
||||
export type VehiclesMessageKey = Exclude<keyof (typeof MESSAGES)['es'], 'transport_types'>;
|
||||
|
||||
function activeLocale(): VehiclesLocale {
|
||||
if (typeof window !== 'undefined') {
|
||||
const htmlLang = document.documentElement?.lang?.toLowerCase() || '';
|
||||
if (htmlLang.startsWith('es')) return 'es';
|
||||
if (htmlLang.startsWith('en')) return 'en';
|
||||
|
||||
const storedLocaleCandidates = [
|
||||
window.localStorage.getItem('locale'),
|
||||
window.localStorage.getItem('lang'),
|
||||
window.localStorage.getItem('language')
|
||||
]
|
||||
.filter(Boolean)
|
||||
.map((value) => String(value).toLowerCase());
|
||||
|
||||
if (storedLocaleCandidates.some((value) => value.startsWith('es'))) return 'es';
|
||||
if (storedLocaleCandidates.some((value) => value.startsWith('en'))) return 'en';
|
||||
}
|
||||
|
||||
return getLocale().startsWith('es') ? 'es' : 'en';
|
||||
}
|
||||
|
||||
export function tv(key: VehiclesMessageKey, vars?: Record<string, string | number>): string {
|
||||
const locale = activeLocale();
|
||||
let text = MESSAGES[locale][key] as string;
|
||||
if (!vars) return text;
|
||||
for (const [name, value] of Object.entries(vars)) {
|
||||
text = text.replaceAll(`{${name}}`, String(value));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
export function tvTransportType(code?: string, fallback?: string): string {
|
||||
const normalizedCode = (code || '').trim().toUpperCase();
|
||||
if (!normalizedCode) return fallback || '';
|
||||
const locale = activeLocale();
|
||||
return MESSAGES[locale].transport_types[normalizedCode as keyof (typeof MESSAGES)['es']['transport_types']] || fallback || '';
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw, Pencil, Trash2 } from 'lucide-svelte';
|
||||
import { Plus, Pencil, Trash2 } from 'lucide-svelte';
|
||||
|
||||
// Importar componentes de la librería
|
||||
import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte';
|
||||
@@ -16,6 +16,7 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosCatalogoSimple } from '$lib/config/shortcuts/dashboard/general_catalogs/common/factory';
|
||||
import { tv } from '$lib/i18n/vehicles';
|
||||
|
||||
// --- ESTADO ---
|
||||
let data = $state<Vehicle[]>([]);
|
||||
@@ -115,7 +116,7 @@
|
||||
if (!selectedVehicle) return;
|
||||
if (
|
||||
!confirm(
|
||||
`¿Estás seguro de eliminar el vehículo "${selectedVehicle.vehicle_key}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`
|
||||
tv('delete_confirm', { key: selectedVehicle.vehicle_key })
|
||||
)
|
||||
) {
|
||||
return;
|
||||
@@ -129,9 +130,9 @@
|
||||
companyStore.activeCompany.id
|
||||
);
|
||||
if (response.error) {
|
||||
alert(`❌ Error al eliminar:\n\n${response.error}`);
|
||||
alert(`${tv('delete_error_title')}\n\n${response.error}`);
|
||||
} else {
|
||||
alert(`✅ Vehículo eliminado correctamente`);
|
||||
alert(tv('delete_success'));
|
||||
selectedIds = [];
|
||||
loadData();
|
||||
}
|
||||
@@ -166,7 +167,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
useShortcuts('Vehículos (transporte)', [
|
||||
useShortcuts(tv('shortcuts_scope'), [
|
||||
...obtenerAtajosCatalogoSimple({
|
||||
manejarNuevo: () => {
|
||||
createDialogOpen = true;
|
||||
@@ -175,7 +176,7 @@
|
||||
}),
|
||||
{
|
||||
key: 'Ctrl+S',
|
||||
description: 'Guardar',
|
||||
description: tv('shortcuts_save'),
|
||||
action: () => {
|
||||
if (!createDialogOpen) return;
|
||||
document.dispatchEvent(new CustomEvent('save-form'));
|
||||
@@ -189,20 +190,20 @@
|
||||
<div class="flex flex-col gap-6 p-6 h-[calc(100svh-4rem)] group-has-data-[collapsible=icon]/sidebar-wrapper:h-[calc(100svh-3rem)] overflow-hidden">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Vehículos (Transporte)</h1>
|
||||
<h1 class="text-2xl font-bold tracking-tight">{tv('page_title')}</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestión del catálogo de camiones y vehículos de transporte
|
||||
{tv('page_subtitle')}
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3"><Button class="h-9" onclick={() => { editingItem = null; createDialogOpen = true; }}><Plus class="mr-2 h-4 w-4" />Nuevo Vehículo</Button></div>
|
||||
<div class="flex items-center gap-3"><Button class="h-9" onclick={() => { editingItem = null; createDialogOpen = true; }}><Plus class="mr-2 h-4 w-4" />{tv('new_vehicle')}</Button></div>
|
||||
</div>
|
||||
|
||||
<Card.Root class="border bg-background flex flex-col">
|
||||
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Vehículos</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Clave" class="h-9 w-40 bg-card lg:w-52" bind:value={searchKey} oninput={handleSearch} /><Input placeholder="Placas" class="h-9 w-40 bg-card lg:w-52" bind:value={searchPlate} oninput={handleSearch} /></div></div></Card.Header>
|
||||
<Card.Content class="p-0">{#if loading && data.length === 0}<div class="flex h-64 items-center justify-center text-muted-foreground">Cargando vehículos...</div>{:else}<div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable {data} {columns} {loading} {hasMore} {loadMore} {selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} onRowClick={handleRowClick} onRowDoubleClick={handleRowDoubleClick} getRowId={(row) => row.vehicle_key} /></div>{/if}</Card.Content>
|
||||
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>{tv('vehicle_list')}</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder={tv('search_key')} class="h-9 w-40 bg-card lg:w-52" bind:value={searchKey} oninput={handleSearch} /><Input placeholder={tv('search_plate')} class="h-9 w-40 bg-card lg:w-52" bind:value={searchPlate} oninput={handleSearch} /></div></div></Card.Header>
|
||||
<Card.Content class="p-0">{#if loading && data.length === 0}<div class="flex h-64 items-center justify-center text-muted-foreground">{tv('loading_vehicles')}</div>{:else}<div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable {data} {columns} {loading} {hasMore} {loadMore} {selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} onRowClick={handleRowClick} onRowDoubleClick={handleRowDoubleClick} getRowId={(row) => row.vehicle_key} /></div>{/if}</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {data.length} de {totalItems} registros</div>
|
||||
<div class="flex-none text-sm text-muted-foreground">{tv('showing_records', { shown: data.length, total: totalItems })}</div>
|
||||
|
||||
<div class="h-20"></div>
|
||||
|
||||
@@ -220,7 +221,7 @@
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
{tv('edit')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -230,7 +231,7 @@
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
{tv('delete')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user