Un comit de guardado de seguridad
This commit is contained in:
@@ -12,6 +12,7 @@ export interface Part {
|
||||
part_number: string;
|
||||
commercial_part_number: string | null;
|
||||
part_class: string | null;
|
||||
material_type?: string | null;
|
||||
|
||||
// Descripciones
|
||||
description_spanish: string | null;
|
||||
@@ -59,6 +60,7 @@ export interface PartCreate {
|
||||
description_english?: string | null;
|
||||
commercial_part_number?: string | null;
|
||||
part_class?: string | null;
|
||||
material_type?: string | null;
|
||||
|
||||
unit_of_measure: string;
|
||||
alternate_unit_measure?: string | null;
|
||||
|
||||
@@ -1,346 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { classesApi, type A76Class } from '$lib/api/dashboard/a76/classes';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import DataTable from '$lib/components/dashboard/classes/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/classes/columns.js';
|
||||
import CreateEditDialog from '$lib/components/dashboard/classes/create-edit-dialog.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Plus, Filter, Trash2, RefreshCw } from 'lucide-svelte';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
// Estado para la lista de classes
|
||||
let allItems = $state<A76Class[]>([]);
|
||||
let currentPage = $state(1);
|
||||
let pageSize = $state(50);
|
||||
let totalItems = $state(0);
|
||||
let loading = $state(false);
|
||||
let hasMore = $derived(allItems.length < totalItems);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
// Estado para filtros
|
||||
let filters = $state({
|
||||
class_code: '',
|
||||
client_id: ''
|
||||
});
|
||||
|
||||
// Estado para el dialog de crear
|
||||
let createDialogOpen = $state(false);
|
||||
|
||||
// Sincronizar token de cookies a localStorage al montar el componente
|
||||
onMount(() => {
|
||||
if (browser) {
|
||||
// Función para obtener el valor de una cookie
|
||||
const getCookie = (name: string): string | null => {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop()?.split(';').shift() || null;
|
||||
return null;
|
||||
};
|
||||
|
||||
// Verificar si hay token en las cookies
|
||||
const cookieToken = getCookie('access_token');
|
||||
const localToken = localStorage.getItem('access_token');
|
||||
|
||||
if (cookieToken && cookieToken !== localToken) {
|
||||
localStorage.setItem('access_token', cookieToken);
|
||||
}
|
||||
|
||||
// También sincronizar refresh_token si existe
|
||||
const cookieRefreshToken = getCookie('refresh_token');
|
||||
const localRefreshToken = localStorage.getItem('refresh_token');
|
||||
|
||||
if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) {
|
||||
localStorage.setItem('refresh_token', cookieRefreshToken);
|
||||
}
|
||||
}
|
||||
|
||||
// Esperar a que el companyStore esté inicializado antes de cargar datos
|
||||
const checkAndLoad = () => {
|
||||
if (companyStore.activeCompany) {
|
||||
loadInitialData();
|
||||
} else {
|
||||
// Si no hay compañía, esperar un poco y reintentar
|
||||
setTimeout(checkAndLoad, 100);
|
||||
}
|
||||
};
|
||||
|
||||
checkAndLoad();
|
||||
|
||||
// También escuchar cambios de compañía para recargar
|
||||
const handleCompanyChange = () => {
|
||||
loadInitialData();
|
||||
};
|
||||
|
||||
window.addEventListener('companyChanged', handleCompanyChange);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('companyChanged', handleCompanyChange);
|
||||
};
|
||||
});
|
||||
|
||||
async function loadInitialData() {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
error = 'No hay compañía seleccionada. Por favor selecciona una compañía en el sidebar.';
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await classesApi.list({
|
||||
company_id: companyId,
|
||||
page: 1,
|
||||
page_size: pageSize
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
console.error('📊 [Classes Page] Error en loadInitialData:', response.error);
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
error = 'Sesión expirada. Recargando página...';
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
} else {
|
||||
error = response.error;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
allItems = response.data.items;
|
||||
currentPage = response.data.page;
|
||||
totalItems = response.data.total;
|
||||
}
|
||||
} catch (e) {
|
||||
error = 'Error cargando los datos';
|
||||
console.error('📊 [Classes Page] Error loading initial data:', e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMore() {
|
||||
if (loading || !hasMore) return;
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) return;
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await classesApi.list({
|
||||
company_id: companyId,
|
||||
page: currentPage + 1,
|
||||
page_size: pageSize
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
console.error('📊 [Classes Page] Error en loadMore:', response.error);
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
error = 'Sesión expirada. Recargando página...';
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
} else {
|
||||
error = response.error;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.data?.items) {
|
||||
allItems = [...allItems, ...response.data.items];
|
||||
currentPage++;
|
||||
totalItems = response.data.total;
|
||||
}
|
||||
} catch (e) {
|
||||
error = 'Error cargando más datos';
|
||||
console.error('📊 [Classes Page] Error loading more:', e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function applyFilters() {
|
||||
// Reset y recargar con filtros
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
error = 'No hay compañía seleccionada';
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
// Aquí podrías agregar filtros adicionales al endpoint si el backend los soporta
|
||||
const response = await classesApi.list({
|
||||
company_id: companyId,
|
||||
page: 1,
|
||||
page_size: pageSize
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
console.error('📊 [Classes Page] Error aplicando filtros:', response.error);
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
error = 'Sesión expirada. Recargando página...';
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
} else {
|
||||
error = response.error;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
allItems = response.data.items;
|
||||
currentPage = 1;
|
||||
totalItems = response.data.total;
|
||||
}
|
||||
} catch (e) {
|
||||
error = 'Error aplicando filtros';
|
||||
console.error('📊 [Classes Page] Error applying filters:', e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function clearFilters() {
|
||||
filters = {
|
||||
class_code: '',
|
||||
client_id: ''
|
||||
};
|
||||
loadInitialData();
|
||||
}
|
||||
|
||||
function reloadData() {
|
||||
loadInitialData();
|
||||
}
|
||||
|
||||
function handleCreateClick() {
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
// Recargar datos después de crear/editar/eliminar
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleDialogSuccess() {
|
||||
createDialogOpen = false;
|
||||
reloadData();
|
||||
}
|
||||
|
||||
// Crear columnas con el callback onSuccess
|
||||
const columns = createColumns(handleSuccess);
|
||||
</script>
|
||||
|
||||
<div class="space-y-6">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold tracking-tight">Clases A76</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestiona las clases de materiales del sistema
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreateClick}>
|
||||
<Plus class="mr-2" size={16} />
|
||||
Nueva Clase
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- Filtros -->
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>Filtros</Card.Title>
|
||||
<Card.Description>Filtra las clases por diferentes criterios</Card.Description>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<form onsubmit={(e) => { e.preventDefault(); applyFilters(); }} class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="filter-class-code">Código de Clase</Label>
|
||||
<Input
|
||||
id="filter-class-code"
|
||||
bind:value={filters.class_code}
|
||||
placeholder="Ej: A76"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label for="filter-client">ID del Cliente</Label>
|
||||
<Input
|
||||
id="filter-client"
|
||||
type="number"
|
||||
bind:value={filters.client_id}
|
||||
placeholder="Ej: 123"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex items-end gap-2 md:col-span-2">
|
||||
<Button type="submit" disabled={loading} class="flex-1">
|
||||
<Filter class="mr-2" size={16} />
|
||||
Filtrar
|
||||
</Button>
|
||||
<Button type="button" variant="outline" onclick={clearFilters} disabled={loading}>
|
||||
<Trash2 size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<!-- Error Message -->
|
||||
{#if error}
|
||||
<Card.Root class="border-destructive">
|
||||
<Card.Header>
|
||||
<Card.Title class="text-destructive">Error</Card.Title>
|
||||
<Card.Description>{error}</Card.Description>
|
||||
</Card.Header>
|
||||
</Card.Root>
|
||||
{/if}
|
||||
|
||||
<!-- Data Table -->
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Card.Title>Listado de Clases</Card.Title>
|
||||
<Card.Description>
|
||||
Mostrando {allItems.length} de {totalItems} registros
|
||||
</Card.Description>
|
||||
</div>
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2" size={16} />
|
||||
Actualizar
|
||||
</Button>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<!-- TanStack DataTable con Infinite Scroll -->
|
||||
<DataTable
|
||||
data={allItems}
|
||||
{columns}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
</div>
|
||||
|
||||
<!-- Dialog de crear nueva clase -->
|
||||
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleDialogSuccess} />
|
||||
@@ -4,7 +4,7 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { createColumns } from '$lib/components/dashboard/ports/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/ports/create-edit-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/units_of_measure/ace/data-table.svelte';
|
||||
import DataTable from '$lib/components/dashboard/general_catalogs/units_of_measure/ace/data-table.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import {
|
||||
ArrowLeft, LoaderCircle, Save, Package, DollarSign,
|
||||
FileText, Settings, Image as ImageIcon, Search,
|
||||
UserCheck, CheckCircle2, XCircle, Tag, Layers
|
||||
UserCheck, CheckCircle2, XCircle, Tag, Layers, Scale
|
||||
} from 'lucide-svelte';
|
||||
|
||||
// Stores & APIs
|
||||
@@ -25,18 +25,19 @@
|
||||
import { partsApi, type PartCreate } from '$lib/api/dashboard/a76/parts';
|
||||
import { clientsProvidersApi } from '$lib/api/dashboard/a76/clients-providers';
|
||||
import { classesApi } from '$lib/api/dashboard/a76/classes';
|
||||
import { materialTypesApi } from '$lib/api/dashboard/a76/material-types';
|
||||
|
||||
import { materialTypesApi } from '$lib/api/dashboard/a76/material-types';
|
||||
|
||||
// Modales
|
||||
import ClientSelectorDialog from '$lib/components/dashboard/parts/client-selector-dialog.svelte';
|
||||
import ClassSelectorDialog from '$lib/components/dashboard/parts/class-selector-dialog.svelte';
|
||||
import MaterialTypeSelectorDialog from '$lib/components/dashboard/parts/material-type-selector-dialog.svelte';
|
||||
|
||||
|
||||
// --- 1. IDENTIFICACIÓN ---
|
||||
let id = $derived($page.params.id === 'new' ? null : Number($page.params.id));
|
||||
let isEdit = $derived(!!id);
|
||||
let title = $derived(isEdit ? "Editar Parte" : "Nueva Parte");
|
||||
|
||||
// --- 2. ESTADOS ---
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
@@ -59,10 +60,10 @@
|
||||
// General
|
||||
description_spanish: '',
|
||||
description_english: '',
|
||||
part_class: '',
|
||||
material_type_key: '',
|
||||
part_class: '',
|
||||
material_type: '', // Corregido: coincide con backend
|
||||
country_of_origin: 'MEX',
|
||||
unit_of_measure: 'PZ',
|
||||
unit_of_measure: 'PZ', // U.M. TIGIE
|
||||
|
||||
// Costos y Pesos
|
||||
unit_weight: 0,
|
||||
@@ -81,14 +82,17 @@
|
||||
// Opcionales 2
|
||||
fda_key: '',
|
||||
|
||||
// Otros
|
||||
// Otros (Comerciales)
|
||||
commercial_part_number: '',
|
||||
alternate_unit_measure: '', // U.M. Comercial
|
||||
|
||||
// Regulatorios
|
||||
fraction: '',
|
||||
eccn: '',
|
||||
license_code: '',
|
||||
export_code: '',
|
||||
exclusion_symbol: '',
|
||||
alternate_unit_measure: '',
|
||||
|
||||
is_active: true
|
||||
});
|
||||
|
||||
@@ -122,8 +126,7 @@
|
||||
description_english: d.description_english || '',
|
||||
|
||||
part_class: d.part_class || '',
|
||||
// OJO: Asegúrate que tu backend devuelva este campo si existe en BD
|
||||
material_type_key: (d as any).material_type_key || '',
|
||||
material_type: d.material_type || '', // Corregido
|
||||
|
||||
country_of_origin: d.country_of_origin || 'MEX',
|
||||
unit_of_measure: d.unit_of_measure || 'PZ',
|
||||
@@ -151,7 +154,7 @@
|
||||
// Cargar datos visuales
|
||||
if (d.client_id) await fetchClientName(d.client_id, companyId);
|
||||
if (d.part_class) await fetchClassDesc(d.part_class, companyId);
|
||||
if ((d as any).material_type_key) await fetchMaterialName((d as any).material_type_key);
|
||||
if (d.material_type) await fetchMaterialName(d.material_type);
|
||||
}
|
||||
} catch (e) {
|
||||
error = "Error al cargar la parte";
|
||||
@@ -161,6 +164,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// --- HELPERS VISUALES ---
|
||||
async function fetchClientName(clientId: number, companyId: number) {
|
||||
try {
|
||||
const res = await clientsProvidersApi.get(clientId, companyId);
|
||||
@@ -194,8 +198,7 @@
|
||||
} catch (e) { console.log("Error visual material", e); }
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- HANDLERS ---
|
||||
function handleClientSelect(client: any) {
|
||||
formData.client_id = client.id;
|
||||
selectedClientName = client.name;
|
||||
@@ -208,10 +211,11 @@
|
||||
}
|
||||
|
||||
function handleMaterialSelect(item: any) {
|
||||
formData.material_type_key = item.key;
|
||||
formData.material_type = item.key;
|
||||
selectedMaterialDesc = item.description;
|
||||
}
|
||||
|
||||
// --- SUBMIT ---
|
||||
async function handleSubmit() {
|
||||
error = null;
|
||||
const activeCompanyId = companyStore.activeCompany?.id;
|
||||
@@ -221,14 +225,12 @@
|
||||
|
||||
loading = true;
|
||||
try {
|
||||
|
||||
const commonData = {
|
||||
|
||||
description_spanish: formData.description_spanish || null,
|
||||
description_english: formData.description_english || null,
|
||||
|
||||
part_class: formData.part_class || null,
|
||||
material_type_key: formData.material_type_key || null,
|
||||
part_class: formData.part_class || null,
|
||||
material_type: formData.material_type || null, // Corregido
|
||||
|
||||
country_of_origin: formData.country_of_origin || 'MEX',
|
||||
unit_of_measure: formData.unit_of_measure,
|
||||
@@ -253,11 +255,9 @@
|
||||
};
|
||||
|
||||
if (isEdit && id) {
|
||||
|
||||
const response = await partsApi.update(id, commonData, activeCompanyId);
|
||||
if (response.error) throw new Error(response.error);
|
||||
} else {
|
||||
|
||||
const createData: PartCreate = {
|
||||
...commonData,
|
||||
company_id: activeCompanyId,
|
||||
@@ -366,7 +366,7 @@
|
||||
</div>
|
||||
<Input
|
||||
id="part_class_material"
|
||||
bind:value={formData.material_type_key}
|
||||
bind:value={formData.material_type}
|
||||
maxlength={8}
|
||||
placeholder="Seleccione Material..."
|
||||
class="pl-9 font-mono cursor-pointer"
|
||||
@@ -387,9 +387,30 @@
|
||||
<p class="text-[10px] text-muted-foreground">Opcional: Clasificación adicional por tipo de material.</p>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2 md:col-span-2">
|
||||
<!-- <div class="grid gap-2">
|
||||
<Label for="country">País Origen (ISO)</Label>
|
||||
<Input id="country" bind:value={formData.country_of_origin} maxlength={3} placeholder="MEX"/>
|
||||
<Input id="country" bind:value={formData.country_of_origin} maxlength={3} placeholder="MEX" class="font-mono"/>
|
||||
</div> -->
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="uom" class="required">Comercial</Label>
|
||||
<Select.Root type="single" bind:value={formData.unit_of_measure}>
|
||||
<Select.Trigger id="uom" class="font-mono">
|
||||
{formData.unit_of_measure || 'Seleccione...'}
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
<Select.Group>
|
||||
<Select.Label>Comunes</Select.Label>
|
||||
<Select.Item value="PZ">Pieza (PZ)</Select.Item>
|
||||
<Select.Item value="KG">Kilogramo (KG)</Select.Item>
|
||||
<Select.Item value="L">Litro (L)</Select.Item>
|
||||
<Select.Item value="M">Metro Lineal (M)</Select.Item>
|
||||
<Select.Item value="M2">Metro Cuadrado (M2)</Select.Item>
|
||||
<Select.Item value="JGO">Juego (JGO)</Select.Item>
|
||||
<Select.Item value="PAR">Par (PAR)</Select.Item>
|
||||
</Select.Group>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -498,6 +519,7 @@
|
||||
</Tabs.Content>
|
||||
|
||||
<Tabs.Content value="otros" class="space-y-6 pt-4 animate-in fade-in duration-300">
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="client_id" class="required">Cliente Asignado</Label>
|
||||
<div class="flex gap-2">
|
||||
@@ -528,15 +550,45 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<Label for="comm_pn">Número de Parte Comercial</Label>
|
||||
<Input id="comm_pn" bind:value={formData.commercial_part_number} maxlength={70} />
|
||||
|
||||
<div class="p-4 border rounded-lg bg-slate-50 dark:bg-slate-900/30 space-y-4">
|
||||
<h3 class="font-medium text-sm text-muted-foreground flex items-center gap-2">
|
||||
<Package class="h-4 w-4"/> Datos Comerciales (Factura)
|
||||
</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="comm_pn">Número de Parte Comercial</Label>
|
||||
<Input id="comm_pn" bind:value={formData.commercial_part_number} maxlength={70} placeholder="Código en factura" />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="alt_um">Unidad de Medida Comercial</Label>
|
||||
<Select.Root type="single" bind:value={formData.alternate_unit_measure}>
|
||||
<Select.Trigger id="alt_um" class="font-mono">
|
||||
{formData.alternate_unit_measure || 'Seleccione...'}
|
||||
</Select.Trigger>
|
||||
<Select.Content class="max-h-[300px]">
|
||||
<Select.Group>
|
||||
<Select.Label>Comunes</Select.Label>
|
||||
<Select.Item value="PZ">Pieza (PZ)</Select.Item>
|
||||
<Select.Item value="KG">Kilogramo (KG)</Select.Item>
|
||||
<Select.Item value="L">Litro (L)</Select.Item>
|
||||
<Select.Item value="M">Metro Lineal (M)</Select.Item>
|
||||
<Select.Item value="M2">Metro Cuadrado (M2)</Select.Item>
|
||||
<Select.Item value="JGO">Juego (JGO)</Select.Item>
|
||||
<Select.Item value="PAR">Par (PAR)</Select.Item>
|
||||
<Select.Item value="SET">Set (SET)</Select.Item>
|
||||
<Select.Item value="CAJA">Caja (CAJA)</Select.Item>
|
||||
<Select.Item value="PK">Paquete (PK)</Select.Item>
|
||||
</Select.Group>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-4 border rounded-lg space-y-4">
|
||||
<Label for="alt_um">UM Conversión (Alterna)</Label>
|
||||
<Input id="alt_um" bind:value={formData.alternate_unit_measure} maxlength={14} />
|
||||
</div>
|
||||
<div class="p-4 border rounded-lg space-y-4 bg-slate-50 dark:bg-slate-900/30">
|
||||
<h3 class="font-medium text-sm text-muted-foreground">Datos Regulatorios</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div class="grid gap-2">
|
||||
@@ -561,6 +613,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3 p-4 border rounded-lg bg-card">
|
||||
<Switch id="is_active" bind:checked={formData.is_active} disabled={loading} />
|
||||
<Label for="is_active">Parte Activa en Sistema</Label>
|
||||
|
||||
Reference in New Issue
Block a user