Merge branch 'fix/transportes-traduccion-erronea' into feature/visible-clicks-csv
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user