Merge branch 'feature/table-invoice' into feature/API-COBE
This commit is contained in:
@@ -40,11 +40,14 @@ import type { ApiResponse } from '$lib/api';
|
||||
export async function getMultiCurrencyTypes(
|
||||
companyId: number,
|
||||
page?: number,
|
||||
pageSize?: number
|
||||
pageSize?: number,
|
||||
filters?: { currency_type_code?: string; country_key?: string }
|
||||
): Promise<ApiResponse<MultiCurrencyTypeListResponse>> {
|
||||
const params = new URLSearchParams({ company_id: companyId.toString() });
|
||||
if (page) params.append('page', page.toString());
|
||||
if (pageSize) params.append('page_size', pageSize.toString());
|
||||
if (filters?.currency_type_code) params.append('currency_type_code', filters.currency_type_code);
|
||||
if (filters?.country_key) params.append('country_key', filters.country_key);
|
||||
|
||||
return api.get<MultiCurrencyTypeListResponse>(`/v1/a76/multi-currency-types/?${params.toString()}`);
|
||||
}
|
||||
|
||||
@@ -273,6 +273,7 @@ export interface UpdatePedimentoData {
|
||||
export interface PedimentoFilters {
|
||||
status?: string;
|
||||
client_id?: number;
|
||||
pedimento?: string;
|
||||
year?: string;
|
||||
sort_by?: string;
|
||||
sort_order?: 'asc' | 'desc' | string;
|
||||
|
||||
@@ -35,13 +35,18 @@ export interface UpdateCodePedimentoRegimenData {
|
||||
*/
|
||||
export const codePedimentoRegimensApi = {
|
||||
/**
|
||||
* Lista todos los code pedimento regimens con paginación
|
||||
* Lista todos los code pedimento regimens con paginación y búsqueda
|
||||
* @param page - Número de página (por defecto 1)
|
||||
* @param pageSize - Tamaño de página (por defecto 50)
|
||||
* @param search - Término de búsqueda (opcional)
|
||||
*/
|
||||
list: (page = 1, pageSize = 50) =>
|
||||
api.get<CodePedimentoRegimenListResponse>(
|
||||
// CORRECTO: Slash antes del signo '?'
|
||||
`/v1/public/reference_data/code-pedimento-regimens/?page=${page}&page_size=${pageSize}`
|
||||
),
|
||||
list: (page = 1, pageSize = 50, search?: string) => {
|
||||
let url = `/v1/public/reference_data/code-pedimento-regimens/?page=${page}&page_size=${pageSize}`;
|
||||
if (search) {
|
||||
url += `&search=${encodeURIComponent(search)}`;
|
||||
}
|
||||
return api.get<CodePedimentoRegimenListResponse>(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtiene un por ID
|
||||
|
||||
@@ -31,15 +31,18 @@ export interface UpdateContainerData {
|
||||
*/
|
||||
export const containersApi = {
|
||||
/**
|
||||
* Lista todos los containers con paginación
|
||||
* Lista todos los containers con paginación y búsqueda
|
||||
* @param page - Número de página (por defecto 1)
|
||||
* @param pageSize - Tamaño de página (por defecto 50)
|
||||
* @param search - Término de búsqueda (opcional)
|
||||
*/
|
||||
list: (page = 1, pageSize = 50) =>
|
||||
api.get<ContainerListResponse>(
|
||||
// CORRECTO: Slash antes del ?
|
||||
`/v1/public/reference_data/containers/?page=${page}&page_size=${pageSize}`
|
||||
),
|
||||
list: (page = 1, pageSize = 50, search?: string) => {
|
||||
let url = `/v1/public/reference_data/containers/?page=${page}&page_size=${pageSize}`;
|
||||
if (search) {
|
||||
url += `&search=${encodeURIComponent(search)}`;
|
||||
}
|
||||
return api.get<ContainerListResponse>(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtiene un container por ID
|
||||
|
||||
@@ -34,15 +34,18 @@ export interface UpdateCurrencyTypeData {
|
||||
*/
|
||||
export const currencyTypesApi = {
|
||||
/**
|
||||
* Lista todos los tipos de moneda con paginación
|
||||
* Lista todos los tipos de moneda con paginación y búsqueda
|
||||
* @param page - Número de página (por defecto 1)
|
||||
* @param pageSize - Tamaño de página (por defecto 50)
|
||||
* @param search - Término de búsqueda (opcional)
|
||||
*/
|
||||
list: (page = 1, pageSize = 50) =>
|
||||
api.get<CurrencyTypeListResponse>(
|
||||
// CORREGIDO: Añadido '/' antes del '?'
|
||||
`/v1/public/reference_data/currency-types/?page=${page}&page_size=${pageSize}`
|
||||
),
|
||||
list: (page = 1, pageSize = 50, search?: string) => {
|
||||
let url = `/v1/public/reference_data/currency-types/?page=${page}&page_size=${pageSize}`;
|
||||
if (search) {
|
||||
url += `&search=${encodeURIComponent(search)}`;
|
||||
}
|
||||
return api.get<CurrencyTypeListResponse>(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtiene un tipo de moneda por código
|
||||
|
||||
@@ -31,14 +31,18 @@ export interface UpdateCustomsSectionData {
|
||||
*/
|
||||
export const customsSectionsApi = {
|
||||
/**
|
||||
* Lista todas las secciones aduaneras con paginación
|
||||
* Lista todas las secciones aduaneras con paginación y búsqueda
|
||||
* @param page - Número de página (por defecto 1)
|
||||
* @param pageSize - Tamaño de página (por defecto 50)
|
||||
* @param search - Término de búsqueda (opcional)
|
||||
*/
|
||||
list: (page = 1, pageSize = 50) =>
|
||||
api.get<CustomsSectionListResponse>(
|
||||
`/v1/public/reference_data/customs-sections/?page=${page}&page_size=${pageSize}`
|
||||
),
|
||||
list: (page = 1, pageSize = 50, search?: string) => {
|
||||
let url = `/v1/public/reference_data/customs-sections/?page=${page}&page_size=${pageSize}`;
|
||||
if (search) {
|
||||
url += `&search=${encodeURIComponent(search)}`;
|
||||
}
|
||||
return api.get<CustomsSectionListResponse>(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtiene una sección aduanera por código
|
||||
|
||||
@@ -34,15 +34,18 @@ export interface UpdateCustomsWarehouseData {
|
||||
*/
|
||||
export const customsWarehousesApi = {
|
||||
/**
|
||||
* Lista todos los recintos fiscalizados con paginación
|
||||
* Lista todos los recintos fiscalizados con paginación y búsqueda
|
||||
* @param page - Número de página (por defecto 1)
|
||||
* @param pageSize - Tamaño de página (por defecto 50)
|
||||
* @param search - Término de búsqueda (opcional)
|
||||
*/
|
||||
list: (page = 1, pageSize = 50) =>
|
||||
api.get<CustomsWarehouseListResponse>(
|
||||
// CORREGIDO: Añadido '/' antes del '?'
|
||||
`/v1/public/reference_data/customs-warehouses/?page=${page}&page_size=${pageSize}`
|
||||
),
|
||||
list: (page = 1, pageSize = 50, search?: string) => {
|
||||
let url = `/v1/public/reference_data/customs-warehouses/?page=${page}&page_size=${pageSize}`;
|
||||
if (search) {
|
||||
url += `&search=${encodeURIComponent(search)}`;
|
||||
}
|
||||
return api.get<CustomsWarehouseListResponse>(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtiene un recinto fiscalizado por clave compuesta (key + customs)
|
||||
|
||||
@@ -34,15 +34,18 @@ export interface UpdateIncotermData {
|
||||
*/
|
||||
export const incotermsApi = {
|
||||
/**
|
||||
* Lista todos los incoterms con paginación
|
||||
* Lista todos los incoterms con paginación y filtros
|
||||
* @param page - Número de página (por defecto 1)
|
||||
* @param pageSize - Tamaño de página (por defecto 50)
|
||||
* @param code - Filtrar por clave (opcional)
|
||||
* @param description - Filtrar por descripción (opcional)
|
||||
*/
|
||||
list: (page = 1, pageSize = 50) =>
|
||||
api.get<IncotermListResponse>(
|
||||
// CORREGIDO: Añadido '/' antes del '?'
|
||||
`/v1/public/reference_data/incoterms/?page=${page}&page_size=${pageSize}`
|
||||
),
|
||||
list: (page = 1, pageSize = 50, code?: string, description?: string) => {
|
||||
let url = `/v1/public/reference_data/incoterms/?page=${page}&page_size=${pageSize}`;
|
||||
if (code) url += `&code=${encodeURIComponent(code)}`;
|
||||
if (description) url += `&description=${encodeURIComponent(description)}`;
|
||||
return api.get<IncotermListResponse>(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtiene un incoterm por código
|
||||
|
||||
@@ -38,12 +38,13 @@ export interface UpdateInvoiceTypeData {
|
||||
*/
|
||||
export const invoiceTypesApi = {
|
||||
/**
|
||||
* Lista todos los tipos de factura con paginación
|
||||
* Lista todos los tipos de factura con paginación y búsqueda
|
||||
* @param page - Número de página (por defecto 1)
|
||||
* @param pageSize - Tamaño de página (por defecto 50)
|
||||
* @param operation - Filtrar por tipo de operación (imp, exp)
|
||||
* @param search - Término de búsqueda (opcional)
|
||||
*/
|
||||
list: (page = 1, pageSize = 50, operation?: string) => {
|
||||
list: (page = 1, pageSize = 50, operation?: string, search?: string) => {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString()
|
||||
@@ -51,8 +52,10 @@ export const invoiceTypesApi = {
|
||||
if (operation) {
|
||||
params.append('operation', operation);
|
||||
}
|
||||
if (search) {
|
||||
params.append('search', search);
|
||||
}
|
||||
return api.get<InvoiceTypeListResponse>(
|
||||
// CORREGIDO: Añadido '/' antes del '?'
|
||||
`/v1/public/reference_data/invoice-types/?${params.toString()}`
|
||||
);
|
||||
},
|
||||
|
||||
@@ -34,12 +34,13 @@
|
||||
*/
|
||||
export const materialTypesApi = {
|
||||
/**
|
||||
* Lista todos los tipos de material con paginación
|
||||
* Lista todos los tipos de material con paginación y búsqueda
|
||||
* @param page - Número de página (por defecto 1)
|
||||
* @param pageSize - Tamaño de página (por defecto 50)
|
||||
* @param type - Filtrar por tipo (ACTIVO FIJO, MATERIALES, PRODUCTOS)
|
||||
* @param search - Término de búsqueda (opcional)
|
||||
*/
|
||||
list: (page = 1, pageSize = 50, type?: string) => {
|
||||
list: (page = 1, pageSize = 50, type?: string, search?: string) => {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString()
|
||||
@@ -47,8 +48,10 @@ export const materialTypesApi = {
|
||||
if (type) {
|
||||
params.append('type', type);
|
||||
}
|
||||
if (search) {
|
||||
params.append('search', search);
|
||||
}
|
||||
return api.get<MaterialTypeListResponse>(
|
||||
// CORRECTO: Ya tiene el '/' antes del '?'
|
||||
`/v1/public/reference_data/material-types/?${params.toString()}`
|
||||
);
|
||||
},
|
||||
|
||||
@@ -31,15 +31,18 @@ export interface UpdatePaymentMethodData {
|
||||
*/
|
||||
export const paymentMethodsApi = {
|
||||
/**
|
||||
* Lista todos los métodos de pago con paginación
|
||||
* Lista todos los métodos de pago con paginación y búsqueda
|
||||
* @param page - Número de página (por defecto 1)
|
||||
* @param pageSize - Tamaño de página (por defecto 50)
|
||||
* @param search - Término de búsqueda (opcional)
|
||||
*/
|
||||
list: (page = 1, pageSize = 50) =>
|
||||
api.get<PaymentMethodListResponse>(
|
||||
// CORREGIDO: Añadido '/' antes del '?'
|
||||
`/v1/public/reference_data/payment-methods/?page=${page}&page_size=${pageSize}`
|
||||
),
|
||||
list: (page = 1, pageSize = 50, search?: string) => {
|
||||
let url = `/v1/public/reference_data/payment-methods/?page=${page}&page_size=${pageSize}`;
|
||||
if (search) {
|
||||
url += `&search=${encodeURIComponent(search)}`;
|
||||
}
|
||||
return api.get<PaymentMethodListResponse>(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtiene un método de pago por key
|
||||
|
||||
@@ -31,15 +31,18 @@ export interface UpdatePedimentoCodeData {
|
||||
*/
|
||||
export const pedimentoCodesApi = {
|
||||
/**
|
||||
* Lista todas las claves de pedimento con paginación
|
||||
* Lista todas las claves de pedimento con paginación y búsqueda
|
||||
* @param page - Número de página (por defecto 1)
|
||||
* @param pageSize - Tamaño de página (por defecto 50)
|
||||
* @param search - Término de búsqueda (opcional)
|
||||
*/
|
||||
list: (page = 1, pageSize = 50) =>
|
||||
api.get<PedimentoCodeListResponse>(
|
||||
// CORREGIDO: Añadido '/' antes del '?'
|
||||
`/v1/public/reference_data/pedimento-codes/?page=${page}&page_size=${pageSize}`
|
||||
),
|
||||
list: (page = 1, pageSize = 50, search?: string) => {
|
||||
let url = `/v1/public/reference_data/pedimento-codes/?page=${page}&page_size=${pageSize}`;
|
||||
if (search) {
|
||||
url += `&search=${encodeURIComponent(search)}`;
|
||||
}
|
||||
return api.get<PedimentoCodeListResponse>(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtiene una clave de pedimento por code
|
||||
|
||||
@@ -31,15 +31,18 @@ export interface UpdatePedimentoRegimenData {
|
||||
*/
|
||||
export const pedimentoRegimensApi = {
|
||||
/**
|
||||
* Lista todos los regímenes de pedimento con paginación
|
||||
* Lista todos los regímenes de pedimento con paginación y búsqueda
|
||||
* @param page - Número de página (por defecto 1)
|
||||
* @param pageSize - Tamaño de página (por defecto 50)
|
||||
* @param search - Término de búsqueda (opcional)
|
||||
*/
|
||||
list: (page = 1, pageSize = 50) =>
|
||||
api.get<PedimentoRegimenListResponse>(
|
||||
// CORREGIDO: Añadido '/' antes del '?'
|
||||
`/v1/public/reference_data/pedimento-regimens/?page=${page}&page_size=${pageSize}`
|
||||
),
|
||||
list: (page = 1, pageSize = 50, search?: string) => {
|
||||
let url = `/v1/public/reference_data/pedimento-regimens/?page=${page}&page_size=${pageSize}`;
|
||||
if (search) {
|
||||
url += `&search=${encodeURIComponent(search)}`;
|
||||
}
|
||||
return api.get<PedimentoRegimenListResponse>(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtiene un régimen de pedimento por code
|
||||
|
||||
@@ -37,15 +37,18 @@ export interface UpdateStateData {
|
||||
*/
|
||||
export const statesApi = {
|
||||
/**
|
||||
* Lista todos los estados con paginación
|
||||
* Lista todos los estados con paginación y búsqueda
|
||||
* @param page - Número de página (por defecto 1)
|
||||
* @param pageSize - Tamaño de página (por defecto 50)
|
||||
* @param search - Término de búsqueda (opcional)
|
||||
*/
|
||||
list: (page = 1, pageSize = 50) =>
|
||||
api.get<StateListResponse>(
|
||||
// CORREGIDO: Añadido '/' antes de '?'
|
||||
`/v1/public/reference_data/states/?page=${page}&page_size=${pageSize}`
|
||||
),
|
||||
list: (page = 1, pageSize = 50, search?: string) => {
|
||||
let url = `/v1/public/reference_data/states/?page=${page}&page_size=${pageSize}`;
|
||||
if (search) {
|
||||
url += `&search=${encodeURIComponent(search)}`;
|
||||
}
|
||||
return api.get<StateListResponse>(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtiene un estado por m3_key
|
||||
|
||||
@@ -31,14 +31,18 @@ export interface UpdateTransportModeData {
|
||||
*/
|
||||
export const transportModesApi = {
|
||||
/**
|
||||
* Lista todos los modos de transporte con paginación
|
||||
* Lista todos los modos de transporte con paginación y búsqueda
|
||||
* @param page - Número de página (por defecto 1)
|
||||
* @param pageSize - Tamaño de página (por defecto 50)
|
||||
* @param search - Término de búsqueda (opcional)
|
||||
*/
|
||||
list: (page = 1, pageSize = 50) =>
|
||||
api.get<TransportModeListResponse>(
|
||||
`/v1/public/reference_data/transport-modes/?page=${page}&page_size=${pageSize}`
|
||||
),
|
||||
list: (page = 1, pageSize = 50, search?: string) => {
|
||||
let url = `/v1/public/reference_data/transport-modes/?page=${page}&page_size=${pageSize}`;
|
||||
if (search) {
|
||||
url += `&search=${encodeURIComponent(search)}`;
|
||||
}
|
||||
return api.get<TransportModeListResponse>(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtiene un modo de transporte por key
|
||||
|
||||
@@ -31,12 +31,18 @@ export interface UpdateTransportTypeData {
|
||||
*/
|
||||
export const transportTypesApi = {
|
||||
/**
|
||||
* Lista todos los tipos de transporte con paginación
|
||||
* Lista todos los tipos de transporte con paginación y búsqueda
|
||||
* @param page - Número de página (por defecto 1)
|
||||
* @param pageSize - Tamaño de página (por defecto 50)
|
||||
* @param search - Término de búsqueda (opcional)
|
||||
*/
|
||||
list: (page = 1, pageSize = 50) =>
|
||||
api.get<TransportTypeListResponse>(
|
||||
`/v1/public/reference_data/transport-types/?page=${page}&page_size=${pageSize}`
|
||||
),
|
||||
list: (page = 1, pageSize = 50, search?: string) => {
|
||||
let url = `/v1/public/reference_data/transport-types/?page=${page}&page_size=${pageSize}`;
|
||||
if (search) {
|
||||
url += `&search=${encodeURIComponent(search)}`;
|
||||
}
|
||||
return api.get<TransportTypeListResponse>(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtiene un tipo de transporte por transport_code
|
||||
|
||||
@@ -31,15 +31,18 @@ export interface UpdateValuationMethodData {
|
||||
*/
|
||||
export const valuationMethodsApi = {
|
||||
/**
|
||||
* Lista todos los métodos de valoración con paginación
|
||||
* Lista todos los métodos de valoración con paginación y búsqueda
|
||||
* @param page - Número de página (por defecto 1)
|
||||
* @param pageSize - Tamaño de página (por defecto 50)
|
||||
* @param search - Término de búsqueda (opcional)
|
||||
*/
|
||||
list: (page = 1, pageSize = 50) =>
|
||||
api.get<ValuationMethodListResponse>(
|
||||
// CORREGIDO: Añadido '/' antes del '?'
|
||||
`/v1/public/reference_data/valuation-methods/?page=${page}&page_size=${pageSize}`
|
||||
),
|
||||
list: (page = 1, pageSize = 50, search?: string) => {
|
||||
let url = `/v1/public/reference_data/valuation-methods/?page=${page}&page_size=${pageSize}`;
|
||||
if (search) {
|
||||
url += `&search=${encodeURIComponent(search)}`;
|
||||
}
|
||||
return api.get<ValuationMethodListResponse>(url);
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtiene un método de valoración por key
|
||||
|
||||
@@ -60,7 +60,10 @@
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<div
|
||||
class="rounded-md border min-h-[400px] max-h-[calc(100vh-310px)] overflow-y-auto"
|
||||
bind:this={scrollContainer}
|
||||
>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
<script lang="ts" generics="TData, TValue">
|
||||
import { onMount } from 'svelte';
|
||||
import { type ColumnDef, getCoreRowModel } from '@tanstack/table-core';
|
||||
import { createSvelteTable, FlexRender } from '$lib/components/ui/data-table/index.js';
|
||||
import * as Table from '$lib/components/ui/table/index.js';
|
||||
|
||||
type InfiniteDataTableProps<TData, TValue> = {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
data: TData[];
|
||||
loading: boolean;
|
||||
hasMore: boolean;
|
||||
loadMore: () => void;
|
||||
emptyMessage?: string;
|
||||
};
|
||||
|
||||
let {
|
||||
data,
|
||||
columns,
|
||||
loading,
|
||||
hasMore,
|
||||
loadMore,
|
||||
emptyMessage = 'No hay resultados.'
|
||||
}: InfiniteDataTableProps<TData, TValue> = $props();
|
||||
|
||||
const table = createSvelteTable({
|
||||
get data() {
|
||||
return data;
|
||||
},
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel()
|
||||
});
|
||||
|
||||
let scrollContainer = $state<HTMLDivElement>();
|
||||
let loadingTrigger = $state<HTMLDivElement>();
|
||||
|
||||
onMount(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const [entry] = entries;
|
||||
if (entry.isIntersecting && hasMore && !loading) {
|
||||
loadMore();
|
||||
}
|
||||
},
|
||||
{
|
||||
root: scrollContainer,
|
||||
threshold: 0.1
|
||||
}
|
||||
);
|
||||
|
||||
if (loadingTrigger) {
|
||||
observer.observe(loadingTrigger);
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="flex h-full w-full flex-col overflow-hidden">
|
||||
<div
|
||||
class="catalog-table-scroll min-h-[320px] max-h-[calc(100svh-280px)]"
|
||||
bind:this={scrollContainer}
|
||||
>
|
||||
<Table.Root>
|
||||
<Table.Header class="catalog-table-header">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
{@const headerList = headerGroup.headers}
|
||||
{@const lastHeaderColId = headerList[headerList.length - 1]?.column.id}
|
||||
<Table.Row>
|
||||
{#each headerList as header (header.id)}
|
||||
{@const colId = header.column.id}
|
||||
<Table.Head
|
||||
class={[
|
||||
'catalog-table-head-cell',
|
||||
colId === lastHeaderColId &&
|
||||
'catalog-table-sticky-right z-30'
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#if table.getRowModel().rows.length}
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
{@const visibleCells = row.getVisibleCells()}
|
||||
{@const lastCellColId = visibleCells[visibleCells.length - 1]?.column.id}
|
||||
<Table.Row
|
||||
data-state={row.getIsSelected() && 'selected'}
|
||||
class="catalog-table-row"
|
||||
>
|
||||
{#each visibleCells as cell (cell.id)}
|
||||
{@const colId = cell.column.id}
|
||||
<Table.Cell
|
||||
class={[
|
||||
'whitespace-nowrap',
|
||||
colId === lastCellColId &&
|
||||
'catalog-table-sticky-right z-10'
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center text-sm text-muted-foreground">
|
||||
{emptyMessage}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center p-0">
|
||||
<div bind:this={loadingTrigger} class="flex h-full w-full items-center justify-center">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-3 rounded-full border bg-muted/30 px-6 py-2 shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-sm font-medium text-foreground">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,7 +71,10 @@
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="max-h-[600px] overflow-y-auto rounded-md border" bind:this={scrollContainer}>
|
||||
<div
|
||||
class="rounded-md border min-h-[400px] max-h-[calc(100vh-310px)] overflow-y-auto"
|
||||
bind:this={scrollContainer}
|
||||
>
|
||||
<Table.Root>
|
||||
<Table.Header class="sticky top-0 z-10 bg-background">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
|
||||
@@ -67,14 +67,18 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="max-h-[600px] overflow-y-auto rounded-md border" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<div class="w-full h-full">
|
||||
<div
|
||||
class="catalog-table-shell min-h-[360px] max-h-[calc(100svh-420px)] overflow-auto [&_[data-slot=table-container]]:w-full"
|
||||
bind:this={scrollContainer}
|
||||
>
|
||||
<div class="min-w-max">
|
||||
<Table.Root>
|
||||
<Table.Header class="catalog-table-header">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
@@ -91,9 +95,7 @@
|
||||
<Table.Row
|
||||
data-state={row.getIsSelected() && 'selected'}
|
||||
onclick={() => onRowClick?.(row.original)}
|
||||
class="cursor-pointer transition-colors {row.getIsSelected()
|
||||
? 'bg-gray-300 dark:bg-gray-600'
|
||||
: 'hover:bg-gray-100 dark:hover:bg-gray-700'}"
|
||||
class="cursor-pointer {row.getIsSelected() ? 'catalog-table-row-selected' : 'catalog-table-row'}"
|
||||
>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
@@ -103,7 +105,7 @@
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center text-sm text-muted-foreground">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
@@ -128,6 +130,7 @@
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</Table.Root>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,67 +38,69 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#if table.getRowModel().rows.length}
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
<div class="flex h-full w-full flex-col overflow-hidden">
|
||||
<div class="relative flex-1 overflow-auto bg-card shadow-inner">
|
||||
<Table.Root>
|
||||
<Table.Header class="sticky top-0 z-20 border-b bg-background/95 shadow-sm backdrop-blur-md">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head class="whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} registros
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#if table.getRowModel().rows.length}
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell class="whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 border-t px-4 py-3">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} registros
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(Number($page.url.searchParams.get('page') || 1) - 1)}
|
||||
disabled={Number($page.url.searchParams.get('page') || 1) <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(Number($page.url.searchParams.get('page') || 1) + 1)}
|
||||
disabled={Number($page.url.searchParams.get('page') || 1) >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(Number($page.url.searchParams.get('page') || 1) - 1)}
|
||||
disabled={Number($page.url.searchParams.get('page') || 1) <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(Number($page.url.searchParams.get('page') || 1) + 1)}
|
||||
disabled={Number($page.url.searchParams.get('page') || 1) >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -40,67 +40,69 @@
|
||||
const currentPage = $derived(Number($page.url.searchParams.get('page') || 1));
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
<div class="flex h-full w-full flex-col overflow-hidden">
|
||||
<div class="relative flex-1 overflow-auto bg-card shadow-inner">
|
||||
<Table.Root>
|
||||
<Table.Header class="sticky top-0 z-20 border-b bg-background/95 shadow-sm backdrop-blur-md">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head class="whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell class="whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4 px-2">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} registros
|
||||
<div class="flex items-center justify-end space-x-2 border-t px-4 py-3">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} registros
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { onMount, untrack } from 'svelte';
|
||||
import { untrack } from 'svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { getSectors, type Sector } from '$lib/api/dashboard/general_catalogs/sectors';
|
||||
import { Loader2, Search } from 'lucide-svelte';
|
||||
@@ -103,34 +102,39 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="space-y-4">
|
||||
|
||||
<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">
|
||||
<h2 class="text-3xl font-bold tracking-tight">{title}</h2>
|
||||
<h1 class="text-2xl font-bold tracking-tight">{title}</h1>
|
||||
</div>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Root class="border bg-background flex min-h-0 flex-1 flex-col">
|
||||
<Card.Header>
|
||||
<div class="flex w-full items-center gap-4">
|
||||
<div class="relative flex-1">
|
||||
<Search class="absolute top-2.5 left-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Buscar por clave o descripción..."
|
||||
class="pl-8"
|
||||
bind:value={searchTerm}
|
||||
oninput={handleSearchInput}
|
||||
onkeydown={handleKeyDown}
|
||||
/>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<Card.Title>Listado de Sectores</Card.Title>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="relative">
|
||||
<Search class="absolute top-2.5 left-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Buscar por clave o descripción..."
|
||||
class="h-9 w-44 bg-card pl-8 lg:w-64"
|
||||
bind:value={searchTerm}
|
||||
oninput={handleSearchInput}
|
||||
onkeydown={handleKeyDown}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<div class="rounded-md border">
|
||||
<Card.Content class="min-h-0 p-0">
|
||||
<div class="catalog-table-shell flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<div class="min-h-0 flex-1 overflow-auto">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.Header class="catalog-table-header">
|
||||
<Table.Row>
|
||||
<Table.Head class="w-[100px]">Clave</Table.Head>
|
||||
<Table.Head>Descripción</Table.Head>
|
||||
<Table.Head class="text-right">Estatus</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell w-[100px]">Clave</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">Descripción</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell text-right">Estatus</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
@@ -150,7 +154,7 @@
|
||||
</Table.Row>
|
||||
{:else}
|
||||
{#each sectors as sector}
|
||||
<Table.Row>
|
||||
<Table.Row class="catalog-table-row">
|
||||
<Table.Cell class="font-medium">{sector.key}</Table.Cell>
|
||||
<Table.Cell>{sector.description}</Table.Cell>
|
||||
<Table.Cell class="text-right">
|
||||
@@ -174,15 +178,13 @@
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex flex-col items-center gap-2">
|
||||
<div class="text-xs text-muted-foreground">
|
||||
Mostrando {sectors.length} de {total} registros
|
||||
</div>
|
||||
<!-- Infinite Scroll Sentinel -->
|
||||
<div bind:this={sentinel} class="h-4 w-full"></div>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {sectors.length} de {total} registros</div>
|
||||
|
||||
<!-- Infinite Scroll Sentinel -->
|
||||
<div bind:this={sentinel} class="h-4 w-full"></div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { onMount, untrack } from 'svelte';
|
||||
import { untrack } from 'svelte';
|
||||
import {
|
||||
getCanadianFractions,
|
||||
deleteCanadianFraction,
|
||||
@@ -8,6 +8,7 @@
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Search, Loader2, Plus, Pencil, Trash2 } from 'lucide-svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import CanadianFractionDialog from './CanadianFractionDialog.svelte';
|
||||
@@ -25,6 +26,7 @@
|
||||
let searchTimeout: ReturnType<typeof setTimeout>;
|
||||
let observer: IntersectionObserver;
|
||||
let sentinel: HTMLDivElement;
|
||||
let scrollContainer: HTMLDivElement;
|
||||
|
||||
// Infinite scroll state
|
||||
let hasMore = $state(true);
|
||||
@@ -138,7 +140,7 @@
|
||||
loadFractions(false);
|
||||
}
|
||||
},
|
||||
{ rootMargin: '100px' }
|
||||
{ root: scrollContainer || null, rootMargin: '100px' }
|
||||
);
|
||||
|
||||
if (sentinel) observer.observe(sentinel);
|
||||
@@ -162,101 +164,99 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex flex-col items-end justify-between gap-4 md:flex-row">
|
||||
<div class="flex max-w-2xl flex-1 items-end gap-4">
|
||||
<div class="flex-1">
|
||||
<label for="search-fraction" class="mb-2 block text-sm font-medium">Buscar</label>
|
||||
<div class="relative">
|
||||
<Search class="absolute top-2.5 left-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
id="search-fraction"
|
||||
type="text"
|
||||
placeholder="Buscar por fracción o descripción..."
|
||||
class="pl-9"
|
||||
bind:value={searchQuery}
|
||||
oninput={handleSearchInput}
|
||||
onkeydown={handleKeyDown}
|
||||
/>
|
||||
<div class="flex min-h-0 flex-1 flex-col gap-4 overflow-hidden">
|
||||
<Card.Root class="border bg-background flex min-h-0 flex-1 flex-col">
|
||||
<Card.Header>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<Card.Title>Listado de Fracciones Canadienses</Card.Title>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="relative">
|
||||
<Search class="absolute top-2.5 left-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
id="search-fraction"
|
||||
type="text"
|
||||
placeholder="Buscar por fracción o descripción..."
|
||||
class="h-9 w-44 bg-card pl-9 lg:w-64"
|
||||
bind:value={searchQuery}
|
||||
oninput={handleSearchInput}
|
||||
onkeydown={handleKeyDown}
|
||||
/>
|
||||
</div>
|
||||
<Button class="h-9" onclick={handleCreate}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Fracción
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button onclick={handleCreate}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Fracción
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.Head>Fracción</Table.Head>
|
||||
<Table.Head>Descripción</Table.Head>
|
||||
<Table.Head>País</Table.Head>
|
||||
<Table.Head>Unidad</Table.Head>
|
||||
<Table.Head class="text-right">ADV</Table.Head>
|
||||
<Table.Head class="w-[100px]">Acciones</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#if fractions.length === 0 && !loading}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={6} class="h-24 text-center"
|
||||
>No se encontraron resultados</Table.Cell
|
||||
>
|
||||
</Table.Row>
|
||||
{:else}
|
||||
{#each fractions as fraction}
|
||||
</Card.Header>
|
||||
<Card.Content class="min-h-0 p-0">
|
||||
<div class="catalog-table-shell flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<div class="min-h-[360px] max-h-[calc(100svh-340px)] flex-1 overflow-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="catalog-table-header">
|
||||
<Table.Row>
|
||||
<Table.Cell class="font-medium">{fraction.fraction}</Table.Cell>
|
||||
<Table.Cell>{fraction.description || '-'}</Table.Cell>
|
||||
<Table.Cell>{fraction.country_code}</Table.Cell>
|
||||
<Table.Cell>{fraction.unit_of_measure || '-'}</Table.Cell>
|
||||
<Table.Cell class="text-right">{fraction.ad_valorem ?? '-'}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
onclick={() => handleEdit(fraction)}
|
||||
>
|
||||
<Pencil class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8 text-destructive hover:text-destructive"
|
||||
onclick={() => handleDelete(fraction)}
|
||||
disabled={deletingFractionId === fraction.id}
|
||||
>
|
||||
{#if deletingFractionId === fraction.id}
|
||||
<Loader2 class="h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="h-4 w-4" />
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
<Table.Head class="catalog-table-head-cell">Fracción</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">Descripción</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">País</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">Unidad</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell text-right">ADV</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell w-[100px]">Acciones</Table.Head>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if loading}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={6} class="h-24 text-center">
|
||||
<div class="flex justify-center">
|
||||
<Loader2 class="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#if fractions.length === 0 && !loading}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={6} class="h-24 text-center">No se encontraron resultados</Table.Cell>
|
||||
</Table.Row>
|
||||
{:else}
|
||||
{#each fractions as fraction}
|
||||
<Table.Row class="catalog-table-row">
|
||||
<Table.Cell class="font-medium">{fraction.fraction}</Table.Cell>
|
||||
<Table.Cell>{fraction.description || '-'}</Table.Cell>
|
||||
<TableCell>{fraction.country_code}</TableCell>
|
||||
<TableCell>{fraction.unit_of_measure || '-'}</TableCell>
|
||||
<TableCell class="text-right">{fraction.ad_valorem ?? '-'}</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" onclick={() => handleEdit(fraction)}>
|
||||
<Pencil class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8 text-destructive hover:text-destructive"
|
||||
onclick={() => handleDelete(fraction)}
|
||||
disabled={deletingFractionId === fraction.id}
|
||||
>
|
||||
{#if deletingFractionId === fraction.id}
|
||||
<Loader2 class="h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="h-4 w-4" />
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if loading}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={6} class="h-24 text-center">
|
||||
<div class="flex justify-center">
|
||||
<Loader2 class="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
<div bind:this={sentinel} class="h-4 w-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<!-- Infinite Scroll Sentinel -->
|
||||
<div bind:this={sentinel} class="h-4 w-full"></div>
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {fractions.length} de {totalItems} registros</div>
|
||||
|
||||
<CanadianFractionDialog
|
||||
bind:open={dialogOpen}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { onMount, untrack } from 'svelte';
|
||||
import { untrack } from 'svelte';
|
||||
import {
|
||||
getHistoricalFractions,
|
||||
deleteHistoricalFraction,
|
||||
@@ -8,23 +8,27 @@
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Search, Loader2, Plus, Pencil, Trash2 } from 'lucide-svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import HistoricalFractionDialog from './HistoricalFractionDialog.svelte';
|
||||
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let fractions = $state<HistoricalFraction[]>([]);
|
||||
let loading = $state(false);
|
||||
let historicalFraction = $state('');
|
||||
let page = $state(1);
|
||||
let totalItems = $state(0);
|
||||
let totalPages = $state(0);
|
||||
let pageSize = 50;
|
||||
let { title = 'Fracciones históricas' }: { title?: string } = $props();
|
||||
|
||||
let searchTimeout: ReturnType<typeof setTimeout>;
|
||||
let observer: IntersectionObserver;
|
||||
let sentinel: HTMLDivElement;
|
||||
let fractions = $state<HistoricalFraction[]>([]);
|
||||
let loading = $state(false);
|
||||
let historicalFraction = $state('');
|
||||
let page = $state(1);
|
||||
let totalItems = $state(0);
|
||||
let totalPages = $state(0);
|
||||
let pageSize = 50;
|
||||
|
||||
let searchTimeout: ReturnType<typeof setTimeout>;
|
||||
let observer: IntersectionObserver;
|
||||
let sentinel: HTMLDivElement;
|
||||
let scrollContainer: HTMLDivElement;
|
||||
|
||||
// Infinite scroll state
|
||||
let hasMore = $state(true);
|
||||
@@ -138,7 +142,10 @@
|
||||
loadFractions(false);
|
||||
}
|
||||
},
|
||||
{ rootMargin: '100px' }
|
||||
{
|
||||
root: scrollContainer || null,
|
||||
rootMargin: '100px'
|
||||
}
|
||||
);
|
||||
|
||||
if (sentinel) observer.observe(sentinel);
|
||||
@@ -163,117 +170,116 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="flex flex-col items-end justify-between gap-4 md:flex-row">
|
||||
<div class="flex max-w-2xl flex-1 items-end gap-4">
|
||||
<div class="flex-1">
|
||||
<label for="search-fraction" class="mb-2 block text-sm font-medium"
|
||||
>Fracción Histórica</label
|
||||
>
|
||||
<div class="relative">
|
||||
<Search class="absolute top-2.5 left-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
id="search-fraction"
|
||||
type="text"
|
||||
placeholder="Buscar fracción..."
|
||||
class="pl-9"
|
||||
bind:value={historicalFraction}
|
||||
oninput={handleSearchInput}
|
||||
onkeydown={handleKeyDown}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="space-y-1">
|
||||
<h1 class="text-2xl font-bold tracking-tight">{title}</h1>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
Gestiona las fracciones históricas de la tarifa.
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={handleCreate}>
|
||||
<Button class="h-9" onclick={handleCreate}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Fracción
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.Head>Fracción</Table.Head>
|
||||
<Table.Head>Tipo</Table.Head>
|
||||
<Table.Head>UM</Table.Head>
|
||||
<Table.Head>País</Table.Head>
|
||||
<Table.Head>Fecha Pub.</Table.Head>
|
||||
<Table.Head>Fecha Fin</Table.Head>
|
||||
<Table.Head class="text-right">IGI</Table.Head>
|
||||
<Table.Head class="text-right">IGE</Table.Head>
|
||||
<Table.Head class="w-[100px]">Acciones</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#if fractions.length === 0 && !loading}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={9} class="h-24 text-center"
|
||||
>No se encontraron resultados</Table.Cell
|
||||
>
|
||||
</Table.Row>
|
||||
{:else}
|
||||
{#each fractions as fraction}
|
||||
<Table.Row>
|
||||
<Table.Cell class="font-medium">{fraction.historical_fraction}</Table.Cell>
|
||||
<Table.Cell>{fraction.fraction_type || '-'}</Table.Cell>
|
||||
<Table.Cell>{fraction.unit_of_measure_code || '-'}</Table.Cell>
|
||||
<Table.Cell>{fraction.country || '-'}</Table.Cell>
|
||||
<Table.Cell
|
||||
>{fraction.publication_date
|
||||
? new Date(fraction.publication_date).toLocaleDateString()
|
||||
: '-'}</Table.Cell
|
||||
>
|
||||
<Table.Cell
|
||||
>{fraction.end_date
|
||||
? new Date(fraction.end_date).toLocaleDateString()
|
||||
: '-'}</Table.Cell
|
||||
>
|
||||
<Table.Cell class="text-right">{fraction.import_tax_rate ?? '-'}</Table.Cell>
|
||||
<Table.Cell class="text-right">{fraction.export_tax_rate ?? '-'}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
onclick={() => handleEdit(fraction)}
|
||||
>
|
||||
<Pencil class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8 text-destructive hover:text-destructive"
|
||||
onclick={() => handleDelete(fraction)}
|
||||
disabled={deletingFractionId === fraction.id}
|
||||
>
|
||||
{#if deletingFractionId === fraction.id}
|
||||
<Loader2 class="h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="h-4 w-4" />
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if loading}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={9} class="h-24 text-center">
|
||||
<div class="flex justify-center">
|
||||
<Loader2 class="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
<Card.Root class="flex min-h-0 flex-1 flex-col border bg-background">
|
||||
<Card.Header>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<Card.Title>Listado de Fracciones Históricas</Card.Title>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="relative">
|
||||
<Search class="absolute top-2.5 left-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
id="search-fraction"
|
||||
type="text"
|
||||
placeholder="Buscar fracción..."
|
||||
class="h-9 w-40 bg-card pl-9 lg:w-56"
|
||||
bind:value={historicalFraction}
|
||||
oninput={handleSearchInput}
|
||||
onkeydown={handleKeyDown}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content class="min-h-0 p-0">
|
||||
<div class="catalog-table-shell flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<div class="min-h-[360px] max-h-[calc(100svh-340px)] flex-1 overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="catalog-table-header">
|
||||
<Table.Row>
|
||||
<Table.Head class="catalog-table-head-cell">Fracción</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">Tipo</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">UM</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">País</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">Fecha Pub.</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell">Fecha Fin</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell text-right">IGI</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell text-right">IGE</Table.Head>
|
||||
<Table.Head class="catalog-table-head-cell w-[100px]">Acciones</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#if fractions.length === 0 && !loading}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={9} class="h-24 text-center">No se encontraron resultados</Table.Cell>
|
||||
</Table.Row>
|
||||
{:else}
|
||||
{#each fractions as fraction}
|
||||
<Table.Row class="catalog-table-row">
|
||||
<Table.Cell class="font-medium">{fraction.historical_fraction}</Table.Cell>
|
||||
<Table.Cell>{fraction.fraction_type || '-'}</Table.Cell>
|
||||
<Table.Cell>{fraction.unit_of_measure_code || '-'}</Table.Cell>
|
||||
<Table.Cell>{fraction.country || '-'}</Table.Cell>
|
||||
<Table.Cell>{fraction.publication_date ? new Date(fraction.publication_date).toLocaleDateString() : '-'}</Table.Cell>
|
||||
<Table.Cell>{fraction.end_date ? new Date(fraction.end_date).toLocaleDateString() : '-'}</Table.Cell>
|
||||
<Table.Cell class="text-right">{fraction.import_tax_rate ?? '-'}</Table.Cell>
|
||||
<Table.Cell class="text-right">{fraction.export_tax_rate ?? '-'}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8" onclick={() => handleEdit(fraction)}>
|
||||
<Pencil class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8 text-destructive hover:text-destructive"
|
||||
onclick={() => handleDelete(fraction)}
|
||||
disabled={deletingFractionId === fraction.id}
|
||||
>
|
||||
{#if deletingFractionId === fraction.id}
|
||||
<Loader2 class="h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="h-4 w-4" />
|
||||
{/if}
|
||||
</Button>
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if loading}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={9} class="h-24 text-center">
|
||||
<div class="flex justify-center">
|
||||
<Loader2 class="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
<div bind:this={sentinel} class="h-4 w-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<!-- Infinite Scroll Sentinel -->
|
||||
<div bind:this={sentinel} class="h-4 w-full"></div>
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {fractions.length} de {totalItems} registros</div>
|
||||
|
||||
<HistoricalFractionDialog
|
||||
bind:open={dialogOpen}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -17,7 +17,7 @@
|
||||
type TariffFraction
|
||||
} from '$lib/api/dashboard/a76/general_catalogs/tariff-fractions';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { onMount, untrack } from 'svelte';
|
||||
import { untrack } from 'svelte';
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||
import TariffFractionFormDialog from './TariffFractionFormDialog.svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
@@ -43,6 +43,7 @@
|
||||
let searchTimeout: ReturnType<typeof setTimeout>;
|
||||
let observer: IntersectionObserver;
|
||||
let sentinel: HTMLDivElement;
|
||||
let scrollContainer: HTMLDivElement;
|
||||
|
||||
// Infinite scroll state
|
||||
let hasMore = $state(true);
|
||||
@@ -121,7 +122,7 @@
|
||||
handlePageChange(currentPage + 1);
|
||||
}
|
||||
},
|
||||
{ rootMargin: '100px' }
|
||||
{ root: scrollContainer || null, rootMargin: '100px' }
|
||||
);
|
||||
|
||||
if (sentinel) observer.observe(sentinel);
|
||||
@@ -180,108 +181,118 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="space-y-4 p-4">
|
||||
<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">
|
||||
<h2 class="text-2xl font-bold tracking-tight">{title}</h2>
|
||||
<h1 class="text-2xl font-bold tracking-tight">{title}</h1>
|
||||
{#if !readOnly}
|
||||
<Button onclick={openCreateDialog}>
|
||||
<Button class="h-9" onclick={openCreateDialog}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Fracción
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="relative max-w-sm flex-1">
|
||||
<Search class="absolute top-2.5 left-2 h-4 w-4 text-muted-foreground" />
|
||||
<Input placeholder="Buscar..." class="pl-8" bind:value={search} oninput={handleSearchInput} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Clave</TableHead>
|
||||
<TableHead>Fracción</TableHead>
|
||||
<TableHead>Descripción</TableHead>
|
||||
{#if catalog === 'mex'}
|
||||
<TableHead>NICO</TableHead>
|
||||
<TableHead>U.M.T</TableHead>
|
||||
{:else}
|
||||
<TableHead>Unidad</TableHead>
|
||||
{/if}
|
||||
<TableHead>Adv. Impo</TableHead>
|
||||
<TableHead>Adv. Expo</TableHead>
|
||||
{#if !readOnly}
|
||||
<TableHead class="w-[100px]">Acciones</TableHead>
|
||||
{/if}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#if fractions.length === 0 && !isLoading}
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colspan={catalog === 'mex' ? (readOnly ? 7 : 8) : readOnly ? 6 : 7}
|
||||
class="h-24 text-center"
|
||||
>
|
||||
No se encontraron resultados
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{:else}
|
||||
{#each fractions as fraction}
|
||||
<Card.Root class="border bg-background flex min-h-0 flex-1 flex-col">
|
||||
<Card.Header>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<Card.Title>Listado de Fracciones</Card.Title>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="relative">
|
||||
<Search class="absolute top-2.5 left-2 h-4 w-4 text-muted-foreground" />
|
||||
<Input placeholder="Buscar..." class="h-9 w-40 bg-card pl-8 lg:w-56" bind:value={search} oninput={handleSearchInput} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content class="min-h-0 p-0">
|
||||
<div class="catalog-table-shell flex min-h-0 flex-1 flex-col overflow-hidden">
|
||||
<div class="min-h-[360px] max-h-[calc(100svh-340px)] flex-1 overflow-auto" bind:this={scrollContainer}>
|
||||
<Table>
|
||||
<TableHeader class="catalog-table-header">
|
||||
<TableRow>
|
||||
<TableCell class="font-mono">{fraction.um_code || fraction.code}</TableCell>
|
||||
<TableCell class="font-medium">{fraction.fraction}</TableCell>
|
||||
<TableCell class="max-w-md truncate" title={fraction.description}>
|
||||
{fraction.description}
|
||||
</TableCell>
|
||||
<TableHead class="catalog-table-head-cell">Clave</TableHead>
|
||||
<TableHead class="catalog-table-head-cell">Fracción</TableHead>
|
||||
<TableHead class="catalog-table-head-cell">Descripción</TableHead>
|
||||
{#if catalog === 'mex'}
|
||||
<TableCell>{fraction.nico || '-'}</TableCell>
|
||||
<TableCell>{fraction.umt || '-'}</TableCell>
|
||||
<TableHead class="catalog-table-head-cell">NICO</TableHead>
|
||||
<TableHead class="catalog-table-head-cell">U.M.T</TableHead>
|
||||
{:else}
|
||||
<TableCell>{fraction.umt || '-'}</TableCell>
|
||||
<TableHead class="catalog-table-head-cell">Unidad</TableHead>
|
||||
{/if}
|
||||
<TableCell>{fraction.adv_impo || '-'}</TableCell>
|
||||
<TableCell>{fraction.adv_expo || '-'}</TableCell>
|
||||
<TableHead class="catalog-table-head-cell">Adv. Impo</TableHead>
|
||||
<TableHead class="catalog-table-head-cell">Adv. Expo</TableHead>
|
||||
{#if !readOnly}
|
||||
<TableCell>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="ghost" size="icon" onclick={() => openEditDialog(fraction)}>
|
||||
<Edit class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="text-destructive hover:text-destructive"
|
||||
onclick={() => confirmDelete(fraction)}
|
||||
>
|
||||
<Trash2 class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableHead class="catalog-table-head-cell w-[100px]">Acciones</TableHead>
|
||||
{/if}
|
||||
</TableRow>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if isLoading}
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colspan={catalog === 'mex' ? (readOnly ? 7 : 8) : readOnly ? 6 : 7}
|
||||
class="h-24 text-center"
|
||||
>
|
||||
<div class="flex justify-center">
|
||||
<Loader2 class="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/if}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{#if fractions.length === 0 && !isLoading}
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colspan={catalog === 'mex' ? (readOnly ? 7 : 8) : readOnly ? 6 : 7}
|
||||
class="h-24 text-center"
|
||||
>
|
||||
No se encontraron resultados
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{:else}
|
||||
{#each fractions as fraction}
|
||||
<TableRow class="catalog-table-row">
|
||||
<TableCell class="font-mono">{fraction.um_code || fraction.code}</TableCell>
|
||||
<TableCell class="font-medium">{fraction.fraction}</TableCell>
|
||||
<TableCell class="max-w-md truncate" title={fraction.description}>
|
||||
{fraction.description}
|
||||
</TableCell>
|
||||
{#if catalog === 'mex'}
|
||||
<TableCell>{fraction.nico || '-'}</TableCell>
|
||||
<TableCell>{fraction.umt || '-'}</TableCell>
|
||||
{:else}
|
||||
<TableCell>{fraction.umt || '-'}</TableCell>
|
||||
{/if}
|
||||
<TableCell>{fraction.adv_impo || '-'}</TableCell>
|
||||
<TableCell>{fraction.adv_expo || '-'}</TableCell>
|
||||
{#if !readOnly}
|
||||
<TableCell>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="ghost" size="icon" onclick={() => openEditDialog(fraction)}>
|
||||
<Edit class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="text-destructive hover:text-destructive"
|
||||
onclick={() => confirmDelete(fraction)}
|
||||
>
|
||||
<Trash2 class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
{/if}
|
||||
</TableRow>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if isLoading}
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colspan={catalog === 'mex' ? (readOnly ? 7 : 8) : readOnly ? 6 : 7}
|
||||
class="h-24 text-center"
|
||||
>
|
||||
<div class="flex justify-center">
|
||||
<Loader2 class="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/if}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<div bind:this={sentinel} class="h-4 w-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<!-- Infinite Scroll Sentinel -->
|
||||
<div bind:this={sentinel} class="h-4 w-full"></div>
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {fractions.length} de {totalFractions} registros</div>
|
||||
</div>
|
||||
|
||||
<AlertDialog.Root bind:open={showDeleteConfirm}>
|
||||
|
||||
@@ -91,7 +91,10 @@
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<div
|
||||
class="rounded-md border min-h-[400px] max-h-[calc(100vh-310px)] overflow-y-auto"
|
||||
bind:this={scrollContainer}
|
||||
>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
|
||||
@@ -99,9 +99,13 @@
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="max-h-[600px] overflow-y-auto rounded-md border" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<div
|
||||
class="catalog-table-shell min-h-[360px] max-h-[calc(100svh-420px)] overflow-auto [&_[data-slot=table-container]]:overflow-visible [&_[data-slot=table-container]]:w-full"
|
||||
bind:this={scrollContainer}
|
||||
>
|
||||
<div class="min-w-max">
|
||||
<Table.Root>
|
||||
<Table.Header class="catalog-table-header">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
{@const headerList = headerGroup.headers}
|
||||
{@const lastHeaderColId = headerList[headerList.length - 1]?.column.id}
|
||||
@@ -111,9 +115,9 @@
|
||||
<Table.Head
|
||||
class={[
|
||||
colId === 'select' &&
|
||||
'sticky left-0 z-40 min-w-[2.75rem] border-r border-border/70 bg-background shadow-[4px_0_12px_-6px_rgba(0,0,0,0.12)] dark:shadow-[4px_0_12px_-6px_rgba(0,0,0,0.35)]',
|
||||
'catalog-table-sticky-left z-40 min-w-[2.75rem]',
|
||||
colId === lastHeaderColId &&
|
||||
'sticky right-0 z-40 border-l border-border/70 bg-background shadow-[-4px_0_12px_-6px_rgba(0,0,0,0.12)] dark:shadow-[-4px_0_12px_-6px_rgba(0,0,0,0.35)]'
|
||||
'catalog-table-sticky-right z-40'
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
@@ -192,9 +196,7 @@
|
||||
{@const lastCellColId = visibleCells[visibleCells.length - 1]?.column.id}
|
||||
<Table.Row
|
||||
data-state={row.getIsSelected() && 'selected'}
|
||||
class="group/inv-list cursor-pointer transition-colors {row.getIsSelected()
|
||||
? 'bg-gray-300 dark:bg-gray-600'
|
||||
: 'hover:bg-gray-100 dark:hover:bg-gray-700'}"
|
||||
class="group/inv-list cursor-pointer {row.getIsSelected() ? 'catalog-table-row-selected' : 'catalog-table-row'}"
|
||||
onclick={() => onRowClick && onRowClick(row.original)}
|
||||
>
|
||||
{#each visibleCells as cell (cell.id)}
|
||||
@@ -202,12 +204,12 @@
|
||||
<Table.Cell
|
||||
class={[
|
||||
colId === 'select' &&
|
||||
'sticky left-0 z-30 min-w-[2.75rem] border-r border-border/70 shadow-[4px_0_12px_-6px_rgba(0,0,0,0.12)] dark:shadow-[4px_0_12px_-6px_rgba(0,0,0,0.35)]',
|
||||
'catalog-table-sticky-left z-30 min-w-[2.75rem]',
|
||||
colId === lastCellColId &&
|
||||
'sticky right-0 z-30 border-l border-border/70 shadow-[-4px_0_12px_-6px_rgba(0,0,0,0.12)] dark:shadow-[-4px_0_12px_-6px_rgba(0,0,0,0.35)]',
|
||||
'catalog-table-sticky-right z-30',
|
||||
row.getIsSelected()
|
||||
? 'bg-gray-300 dark:bg-gray-600'
|
||||
: 'bg-background group-hover/inv-list:bg-gray-100 dark:group-hover/inv-list:bg-gray-700'
|
||||
? 'catalog-table-sticky-row-selected'
|
||||
: 'catalog-table-sticky-row-hover'
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')}
|
||||
@@ -244,6 +246,7 @@
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</Table.Root>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -97,16 +97,25 @@
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-auto" bind:this={scrollContainer}>
|
||||
<Table.Root class="w-full">
|
||||
<Table.Header class="bg-background sticky top-0 z-10">
|
||||
<div
|
||||
class="min-h-[360px] max-h-[calc(100svh-420px)] overflow-auto rounded-md border bg-background [&_[data-slot=table-container]]:overflow-visible [&_[data-slot=table-container]]:w-full"
|
||||
bind:this={scrollContainer}
|
||||
>
|
||||
<div class="min-w-max">
|
||||
<Table.Root>
|
||||
<Table.Header class="sticky top-0 z-20 border-b bg-background/95 shadow-sm backdrop-blur-md">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head class="whitespace-nowrap p-0 {header.column.columnDef.meta?.className || ''}">
|
||||
{@const colId = header.column.id}
|
||||
<Table.Head
|
||||
class="whitespace-nowrap text-sm font-semibold text-foreground/90 p-0 {header.column.columnDef.meta?.className || ''} {colId === 'select'
|
||||
? 'sticky left-0 z-40 min-w-[2.75rem] border-r border-border/70 bg-background shadow-[4px_0_12px_-6px_rgba(0,0,0,0.12)] dark:shadow-[4px_0_12px_-6px_rgba(0,0,0,0.35)]'
|
||||
: ''}"
|
||||
>
|
||||
{#if !header.isPlaceholder}
|
||||
<button
|
||||
class="group flex h-full w-full items-center gap-2 px-2 py-2 text-left hover:bg-muted/50"
|
||||
class="group flex h-full w-full items-center justify-between gap-2 px-2 py-2 text-left {header.column.getCanSort() ? 'cursor-pointer select-none' : ''}"
|
||||
onclick={header.column.getToggleSortingHandler()}
|
||||
disabled={!header.column.getCanSort()}
|
||||
>
|
||||
@@ -178,10 +187,19 @@
|
||||
}
|
||||
}}
|
||||
ondblclick={() => handleRowDoubleClick(row)}
|
||||
class="cursor-pointer hover:bg-muted/50 transition-colors {row.getIsSelected() ? 'bg-primary/10' : ''}"
|
||||
class="group/inv-list cursor-pointer transition-colors {row.getIsSelected()
|
||||
? 'bg-gray-300 dark:bg-gray-600'
|
||||
: 'hover:bg-gray-100 dark:hover:bg-gray-700'}"
|
||||
>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell class="whitespace-nowrap {cell.column.columnDef.meta?.className || ''}">
|
||||
{@const colId = cell.column.id}
|
||||
<Table.Cell
|
||||
class="whitespace-nowrap {cell.column.columnDef.meta?.className || ''} {colId === 'select'
|
||||
? 'sticky left-0 z-30 min-w-[2.75rem] border-r border-border/70 bg-background shadow-[4px_0_12px_-6px_rgba(0,0,0,0.12)] dark:shadow-[4px_0_12px_-6px_rgba(0,0,0,0.35)]'
|
||||
: ''} {row.getIsSelected()
|
||||
? 'bg-gray-300 dark:bg-gray-600'
|
||||
: 'bg-background group-hover/inv-list:bg-gray-100 dark:group-hover/inv-list:bg-gray-700'}"
|
||||
>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
@@ -217,6 +235,7 @@
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</Table.Root>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,19 +12,6 @@ export type CodePedimentoRegimen = {
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<CodePedimentoRegimen>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "id",
|
||||
header: "ID",
|
||||
cell: ({ row }) => {
|
||||
const idSnippet = createRawSnippet<[{ id: number }]>((getId) => {
|
||||
const { id } = getId();
|
||||
return {
|
||||
render: () => `<div class="font-medium">${id}</div>`
|
||||
};
|
||||
});
|
||||
return renderSnippet(idSnippet, { id: row.original.id });
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "pedimento_code",
|
||||
header: "Código Pedimento",
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { CodePedimentoRegimen } from "./columns.js";
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
@@ -16,8 +14,6 @@
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.id.toString());
|
||||
@@ -26,14 +22,6 @@
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -54,13 +42,8 @@
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
<CreateEditDialog bind:open={showEditDialog} item={item} {onSuccess} />
|
||||
<DeleteDialog bind:open={showDeleteDialog} {item} {onSuccess} />
|
||||
|
||||
@@ -59,65 +59,67 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<div class="flex flex-col h-full w-full overflow-hidden">
|
||||
<div class="relative min-h-[320px] max-h-[calc(100svh-340px)] flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<table class="w-full caption-bottom text-sm border-collapse">
|
||||
<thead class="sticky top-0 z-20 bg-background/95 shadow-sm backdrop-blur-md border-b">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
<tr>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
</th>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0">
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</td>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
<!-- Loading Trigger -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center p-0">
|
||||
<div bind:this={loadingTrigger} class="flex items-center justify-center h-full w-full">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
<div class="flex items-center justify-center gap-3 bg-muted/30 px-6 py-2 rounded-full border shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-foreground font-medium text-sm">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
<div class="text-muted-foreground text-sm flex items-center gap-2">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { Container } from "./columns.js";
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
@@ -16,8 +14,6 @@
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.key.toString());
|
||||
@@ -26,14 +22,6 @@
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -54,13 +42,8 @@
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
<CreateEditDialog bind:open={showEditDialog} item={item} {onSuccess} />
|
||||
<DeleteDialog bind:open={showDeleteDialog} {item} {onSuccess} />
|
||||
|
||||
@@ -59,65 +59,67 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<div class="flex flex-col h-full w-full overflow-hidden">
|
||||
<div class="relative min-h-[320px] max-h-[calc(100svh-340px)] flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<table class="w-full caption-bottom text-sm border-collapse">
|
||||
<thead class="sticky top-0 z-20 bg-background/95 shadow-sm backdrop-blur-md border-b">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
<tr>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
</th>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0">
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</td>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
<!-- Loading Trigger -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center p-0">
|
||||
<div bind:this={loadingTrigger} class="flex items-center justify-center h-full w-full">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
<div class="flex items-center justify-center gap-3 bg-muted/30 px-6 py-2 rounded-full border shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-foreground font-medium text-sm">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
<div class="text-muted-foreground text-sm flex items-center gap-2">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { Country } from "./columns.js";
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
@@ -16,8 +14,6 @@
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.m3_key.toString());
|
||||
@@ -26,14 +22,6 @@
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -54,13 +42,8 @@
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
<CreateEditDialog bind:open={showEditDialog} item={item} {onSuccess} />
|
||||
<DeleteDialog bind:open={showDeleteDialog} {item} {onSuccess} />
|
||||
|
||||
@@ -59,65 +59,67 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<div class="flex flex-col h-full w-full overflow-hidden">
|
||||
<div class="relative min-h-[320px] max-h-[calc(100svh-340px)] flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<table class="w-full caption-bottom text-sm border-collapse">
|
||||
<thead class="sticky top-0 z-20 bg-background/95 shadow-sm backdrop-blur-md border-b">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
<tr>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
</th>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0">
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</td>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
<!-- Loading Trigger -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center p-0">
|
||||
<div bind:this={loadingTrigger} class="flex items-center justify-center h-full w-full">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
<div class="flex items-center justify-center gap-3 bg-muted/30 px-6 py-2 rounded-full border shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-foreground font-medium text-sm">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
<div class="text-muted-foreground text-sm flex items-center gap-2">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { CurrencyType } from "./columns.js";
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
@@ -16,8 +14,6 @@
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.code.toString());
|
||||
@@ -26,14 +22,6 @@
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -54,13 +42,8 @@
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
<CreateEditDialog bind:open={showEditDialog} item={item} {onSuccess} />
|
||||
<DeleteDialog bind:open={showDeleteDialog} {item} {onSuccess} />
|
||||
|
||||
@@ -59,65 +59,67 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<div class="flex flex-col h-full w-full overflow-hidden">
|
||||
<div class="relative min-h-[320px] max-h-[calc(100svh-340px)] flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<table class="w-full caption-bottom text-sm border-collapse">
|
||||
<thead class="sticky top-0 z-20 bg-background/95 shadow-sm backdrop-blur-md border-b">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
<tr>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
</th>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0">
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</td>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
<!-- Loading Trigger -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center p-0">
|
||||
<div bind:this={loadingTrigger} class="flex items-center justify-center h-full w-full">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
<div class="flex items-center justify-center gap-3 bg-muted/30 px-6 py-2 rounded-full border shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-foreground font-medium text-sm">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
<div class="text-muted-foreground text-sm flex items-center gap-2">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { CustomsSection } from "./columns.js";
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
@@ -16,8 +14,6 @@
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.customs_code.toString());
|
||||
@@ -26,14 +22,6 @@
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -54,13 +42,8 @@
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
<CreateEditDialog bind:open={showEditDialog} item={item} {onSuccess} />
|
||||
<DeleteDialog bind:open={showDeleteDialog} {item} {onSuccess} />
|
||||
|
||||
@@ -59,65 +59,67 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<div class="flex col h-full w-full overflow-hidden">
|
||||
<div class="relative min-h-[320px] max-h-[calc(100svh-340px)] flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<table class="w-full caption-bottom text-sm border-collapse">
|
||||
<thead class="sticky top-0 z-20 bg-background/95 shadow-sm backdrop-blur-md border-b">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
<tr>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
</th>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0">
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</td>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
<!-- Loading Trigger -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center p-0">
|
||||
<div bind:this={loadingTrigger} class="flex items-center justify-center h-full w-full">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
<div class="flex items-center justify-center gap-3 bg-muted/30 px-6 py-2 rounded-full border shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-foreground font-medium text-sm">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
<div class="text-muted-foreground text-sm flex items-center gap-2">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { CustomsWarehouse } from "./columns.js";
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
@@ -16,8 +14,6 @@
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(`${item.key}|${item.customs}`);
|
||||
@@ -26,14 +22,6 @@
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -54,13 +42,8 @@
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
<CreateEditDialog bind:open={showEditDialog} item={item} {onSuccess} />
|
||||
<DeleteDialog bind:open={showDeleteDialog} {item} {onSuccess} />
|
||||
|
||||
@@ -59,65 +59,67 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<div class="flex flex-col h-full w-full overflow-hidden">
|
||||
<div class="relative min-h-[320px] max-h-[calc(100svh-340px)] flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<table class="w-full caption-bottom text-sm border-collapse">
|
||||
<thead class="sticky top-0 z-20 bg-background/95 shadow-sm backdrop-blur-md border-b">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
<tr>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
</th>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0">
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</td>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
<!-- Loading Trigger -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center p-0">
|
||||
<div bind:this={loadingTrigger} class="flex items-center justify-center h-full w-full">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
<div class="flex items-center justify-center gap-3 bg-muted/30 px-6 py-2 rounded-full border shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-foreground font-medium text-sm">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
<div class="text-muted-foreground text-sm flex items-center gap-2">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { Incoterm } from "./columns.js";
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
@@ -16,8 +14,6 @@
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.code.toString());
|
||||
@@ -26,14 +22,6 @@
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -54,13 +42,8 @@
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
<CreateEditDialog bind:open={showEditDialog} item={item} {onSuccess} />
|
||||
<DeleteDialog bind:open={showDeleteDialog} {item} {onSuccess} />
|
||||
|
||||
@@ -3,104 +3,114 @@
|
||||
type ColumnDef,
|
||||
getCoreRowModel
|
||||
} from "@tanstack/table-core";
|
||||
import { onMount } from "svelte";
|
||||
import { createSvelteTable, FlexRender } from "$lib/components/ui/data-table/index.js";
|
||||
import * as Table from "$lib/components/ui/table/index.js";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
|
||||
type DataTableProps<TData, TValue> = {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
data: TData[];
|
||||
pageCount: number;
|
||||
totalItems: number;
|
||||
loading: boolean;
|
||||
hasMore: boolean;
|
||||
loadMore: () => void;
|
||||
};
|
||||
|
||||
let {
|
||||
data,
|
||||
columns,
|
||||
pageCount,
|
||||
totalItems
|
||||
loading,
|
||||
hasMore,
|
||||
loadMore
|
||||
}: DataTableProps<TData, TValue> = $props();
|
||||
|
||||
const table = createSvelteTable({
|
||||
get data() { return data; },
|
||||
get columns() { return columns; },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
get pageCount() { return pageCount; },
|
||||
getCoreRowModel: getCoreRowModel()
|
||||
});
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}
|
||||
let scrollContainer = $state<HTMLDivElement>();
|
||||
let loadingTrigger = $state<HTMLDivElement>();
|
||||
|
||||
const currentPage = $derived(Number($page.url.searchParams.get('page') || 1));
|
||||
onMount(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const [entry] = entries;
|
||||
if (entry.isIntersecting && hasMore && !loading) {
|
||||
loadMore();
|
||||
}
|
||||
},
|
||||
{ root: scrollContainer, threshold: 0.1 }
|
||||
);
|
||||
|
||||
if (loadingTrigger) observer.observe(loadingTrigger);
|
||||
|
||||
return () => observer.disconnect();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<div class="flex flex-col h-full w-full overflow-hidden">
|
||||
<div class="relative min-h-[320px] max-h-[calc(100svh-340px)] flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<table class="w-full caption-bottom text-sm border-collapse">
|
||||
<thead class="sticky top-0 z-20 bg-background/95 shadow-sm backdrop-blur-md border-b">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<tr>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</th>
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0">
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4 px-2">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} registros
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</td>
|
||||
{/each}
|
||||
</tr>
|
||||
{:else}
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger -->
|
||||
{#if hasMore}
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center p-0">
|
||||
<div bind:this={loadingTrigger} class="flex items-center justify-center h-full w-full">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-3 bg-muted/30 px-6 py-2 rounded-full border shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-foreground font-medium text-sm">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm flex items-center gap-2">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { InvoiceType } from "./columns.js";
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
@@ -16,8 +14,6 @@
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.key.toString());
|
||||
@@ -26,14 +22,6 @@
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -54,13 +42,8 @@
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
<CreateEditDialog bind:open={showEditDialog} item={item} {onSuccess} />
|
||||
<DeleteDialog bind:open={showDeleteDialog} {item} {onSuccess} />
|
||||
|
||||
@@ -59,65 +59,67 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<div class="flex flex-col h-full w-full overflow-hidden">
|
||||
<div class="relative min-h-[320px] max-h-[calc(100svh-340px)] flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<table class="w-full caption-bottom text-sm border-collapse">
|
||||
<thead class="sticky top-0 z-20 bg-background/95 shadow-sm backdrop-blur-md border-b">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
<tr>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
</th>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0">
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</td>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
<!-- Loading Trigger -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center p-0">
|
||||
<div bind:this={loadingTrigger} class="flex items-center justify-center h-full w-full">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
<div class="flex items-center justify-center gap-3 bg-muted/30 px-6 py-2 rounded-full border shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-foreground font-medium text-sm">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
<div class="text-muted-foreground text-sm flex items-center gap-2">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { MaterialType } from "./columns.js";
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
@@ -16,8 +14,6 @@
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.key.toString());
|
||||
@@ -26,14 +22,6 @@
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -54,13 +42,8 @@
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
<CreateEditDialog bind:open={showEditDialog} item={item} {onSuccess} />
|
||||
<DeleteDialog bind:open={showDeleteDialog} {item} {onSuccess} />
|
||||
|
||||
@@ -59,65 +59,67 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<div class="flex flex-col h-full w-full overflow-hidden">
|
||||
<div class="relative min-h-[320px] max-h-[calc(100svh-340px)] flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<table class="w-full caption-bottom text-sm border-collapse">
|
||||
<thead class="sticky top-0 z-20 bg-background/95 shadow-sm backdrop-blur-md border-b">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
<tr>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
</th>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0">
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</td>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
<!-- Loading Trigger -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center p-0">
|
||||
<div bind:this={loadingTrigger} class="flex items-center justify-center h-full w-full">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
<div class="flex items-center justify-center gap-3 bg-muted/30 px-6 py-2 rounded-full border shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-foreground font-medium text-sm">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
<div class="text-muted-foreground text-sm flex items-center gap-2">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { PaymentMethod } from "./columns.js";
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
@@ -16,8 +14,6 @@
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.key.toString());
|
||||
@@ -26,14 +22,6 @@
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -54,13 +42,8 @@
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
<CreateEditDialog bind:open={showEditDialog} item={item} {onSuccess} />
|
||||
<DeleteDialog bind:open={showDeleteDialog} {item} {onSuccess} />
|
||||
|
||||
@@ -59,65 +59,67 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<div class="flex flex-col h-full w-full overflow-hidden">
|
||||
<div class="relative min-h-[320px] max-h-[calc(100svh-340px)] flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<table class="w-full caption-bottom text-sm border-collapse">
|
||||
<thead class="sticky top-0 z-20 bg-background/95 shadow-sm backdrop-blur-md border-b">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
<tr>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
</th>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0">
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</td>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
<!-- Loading Trigger -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center p-0">
|
||||
<div bind:this={loadingTrigger} class="flex items-center justify-center h-full w-full">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
<div class="flex items-center justify-center gap-3 bg-muted/30 px-6 py-2 rounded-full border shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-foreground font-medium text-sm">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
<div class="text-muted-foreground text-sm flex items-center gap-2">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { PedimentoCode } from "./columns.js";
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
@@ -16,8 +14,6 @@
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.code.toString());
|
||||
@@ -26,14 +22,6 @@
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -54,13 +42,8 @@
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
<CreateEditDialog bind:open={showEditDialog} item={item} {onSuccess} />
|
||||
<DeleteDialog bind:open={showDeleteDialog} {item} {onSuccess} />
|
||||
|
||||
@@ -59,65 +59,67 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<div class="flex flex-col h-full w-full overflow-hidden">
|
||||
<div class="relative min-h-[320px] max-h-[calc(100svh-340px)] flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<table class="w-full caption-bottom text-sm border-collapse">
|
||||
<thead class="sticky top-0 z-20 bg-background/95 shadow-sm backdrop-blur-md border-b">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
<tr>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
</th>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0">
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</td>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
<!-- Loading Trigger -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center p-0">
|
||||
<div bind:this={loadingTrigger} class="flex items-center justify-center h-full w-full">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
<div class="flex items-center justify-center gap-3 bg-muted/30 px-6 py-2 rounded-full border shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-foreground font-medium text-sm">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
<div class="text-muted-foreground text-sm flex items-center gap-2">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { PedimentoRegimen } from "./columns.js";
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
@@ -16,8 +14,6 @@
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.code.toString());
|
||||
@@ -26,14 +22,6 @@
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -54,13 +42,8 @@
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
<CreateEditDialog bind:open={showEditDialog} item={item} {onSuccess} />
|
||||
<DeleteDialog bind:open={showDeleteDialog} {item} {onSuccess} />
|
||||
|
||||
@@ -59,65 +59,67 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<div class="flex flex-col h-full w-full overflow-hidden">
|
||||
<div class="relative min-h-[320px] max-h-[calc(100svh-340px)] flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<table class="w-full caption-bottom text-sm border-collapse">
|
||||
<thead class="sticky top-0 z-20 bg-background/95 shadow-sm backdrop-blur-md border-b">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
<tr>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
</th>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0">
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</td>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
<!-- Loading Trigger -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center p-0">
|
||||
<div bind:this={loadingTrigger} class="flex items-center justify-center h-full w-full">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
<div class="flex items-center justify-center gap-3 bg-muted/30 px-6 py-2 rounded-full border shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-foreground font-medium text-sm">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
<div class="text-muted-foreground text-sm flex items-center gap-2">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -25,6 +25,19 @@
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
function getActiveCompanyId(): number | null {
|
||||
const fromStore = companyStore.activeCompany?.id;
|
||||
if (fromStore) return fromStore;
|
||||
if (typeof document === 'undefined') return null;
|
||||
const cookie = document.cookie
|
||||
.split('; ')
|
||||
.find((row) => row.startsWith('active_company_id='))
|
||||
?.split('=')[1];
|
||||
if (!cookie) return null;
|
||||
const parsed = Number(cookie);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
// Actualizar formData cuando item cambia
|
||||
$effect(() => {
|
||||
if (item) {
|
||||
@@ -49,7 +62,7 @@
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
const companyId = getActiveCompanyId();
|
||||
if (!companyId) {
|
||||
error = 'No hay empresa activa seleccionada';
|
||||
loading = false;
|
||||
|
||||
@@ -59,65 +59,67 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<div class="flex flex-col h-full w-full overflow-hidden">
|
||||
<div class="relative min-h-[320px] max-h-[calc(100svh-340px)] flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<table class="w-full caption-bottom text-sm border-collapse">
|
||||
<thead class="sticky top-0 z-20 bg-background/95 shadow-sm backdrop-blur-md border-b">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
<tr>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
</th>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0">
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</td>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
<!-- Loading Trigger -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center p-0">
|
||||
<div bind:this={loadingTrigger} class="flex items-center justify-center h-full w-full">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
<div class="flex items-center justify-center gap-3 bg-muted/30 px-6 py-2 rounded-full border shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-foreground font-medium text-sm">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
<div class="text-muted-foreground text-sm flex items-center gap-2">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,10 +18,23 @@
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
function getActiveCompanyId(): number | null {
|
||||
const fromStore = companyStore.activeCompany?.id;
|
||||
if (fromStore) return fromStore;
|
||||
if (typeof document === 'undefined') return null;
|
||||
const cookie = document.cookie
|
||||
.split('; ')
|
||||
.find((row) => row.startsWith('active_company_id='))
|
||||
?.split('=')[1];
|
||||
if (!cookie) return null;
|
||||
const parsed = Number(cookie);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
|
||||
async function handleDelete() {
|
||||
if (!item) return;
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
const companyId = getActiveCompanyId();
|
||||
if (!companyId) {
|
||||
error = 'No hay empresa activa seleccionada';
|
||||
return;
|
||||
|
||||
@@ -59,65 +59,67 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<div class="flex flex-col h-full w-full overflow-hidden">
|
||||
<div class="relative min-h-[320px] max-h-[calc(100svh-340px)] flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<table class="w-full caption-bottom text-sm border-collapse">
|
||||
<thead class="sticky top-0 z-20 bg-background/95 shadow-sm backdrop-blur-md border-b">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
<tr>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
</th>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0">
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</td>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
<!-- Loading Trigger -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center p-0">
|
||||
<div bind:this={loadingTrigger} class="flex items-center justify-center h-full w-full">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
<div class="flex items-center justify-center gap-3 bg-muted/30 px-6 py-2 rounded-full border shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-foreground font-medium text-sm">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
<div class="text-muted-foreground text-sm flex items-center gap-2">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { TransportMode } from "./columns.js";
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
@@ -16,8 +14,6 @@
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.key.toString());
|
||||
@@ -26,14 +22,6 @@
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -54,13 +42,8 @@
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
<CreateEditDialog bind:open={showEditDialog} item={item} {onSuccess} />
|
||||
<DeleteDialog bind:open={showDeleteDialog} {item} {onSuccess} />
|
||||
|
||||
@@ -59,65 +59,67 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<div class="flex flex-col h-full w-full overflow-hidden">
|
||||
<div class="relative min-h-[320px] max-h-[calc(100svh-340px)] flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<table class="w-full caption-bottom text-sm border-collapse">
|
||||
<thead class="sticky top-0 z-20 bg-background/95 shadow-sm backdrop-blur-md border-b">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
<tr>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
</th>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0">
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</td>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
<!-- Loading Trigger -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center p-0">
|
||||
<div bind:this={loadingTrigger} class="flex items-center justify-center h-full w-full">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
<div class="flex items-center justify-center gap-3 bg-muted/30 px-6 py-2 rounded-full border shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-foreground font-medium text-sm">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
<div class="text-muted-foreground text-sm flex items-center gap-2">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { TransportType } from "./columns.js";
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
@@ -16,8 +14,6 @@
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.transport_code.toString());
|
||||
@@ -26,14 +22,6 @@
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -54,13 +42,8 @@
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
<CreateEditDialog bind:open={showEditDialog} item={item} {onSuccess} />
|
||||
<DeleteDialog bind:open={showDeleteDialog} {item} {onSuccess} />
|
||||
|
||||
@@ -59,65 +59,67 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<div class="flex flex-col h-full w-full overflow-hidden">
|
||||
<div class="relative min-h-[320px] max-h-[calc(100svh-340px)] flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<table class="w-full caption-bottom text-sm border-collapse">
|
||||
<thead class="sticky top-0 z-20 bg-background/95 shadow-sm backdrop-blur-md border-b">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
<tr>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
</th>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0">
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</td>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
<!-- Loading Trigger -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center p-0">
|
||||
<div bind:this={loadingTrigger} class="flex items-center justify-center h-full w-full">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
<div class="flex items-center justify-center gap-3 bg-muted/30 px-6 py-2 rounded-full border shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-foreground font-medium text-sm">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
<div class="text-muted-foreground text-sm flex items-center gap-2">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
import { Button } from "$lib/components/ui/button/index.js";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu/index.js";
|
||||
import type { ValuationMethod } from "./columns.js";
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DetailsDialog from "./details-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
@@ -16,8 +14,6 @@
|
||||
} = $props();
|
||||
|
||||
let showDetailsDialog = $state(false);
|
||||
let showEditDialog = $state(false);
|
||||
let showDeleteDialog = $state(false);
|
||||
|
||||
function handleCopyId() {
|
||||
navigator.clipboard.writeText(item.key.toString());
|
||||
@@ -26,14 +22,6 @@
|
||||
function handleViewDetails() {
|
||||
showDetailsDialog = true;
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
showEditDialog = true;
|
||||
}
|
||||
|
||||
function handleDelete() {
|
||||
showDeleteDialog = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -54,13 +42,8 @@
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleViewDetails}>Ver detalles</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleEdit}>Editar</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive" onclick={handleDelete}>Eliminar</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<!-- Dialogs -->
|
||||
<DetailsDialog bind:open={showDetailsDialog} {item} />
|
||||
<CreateEditDialog bind:open={showEditDialog} item={item} {onSuccess} />
|
||||
<DeleteDialog bind:open={showDeleteDialog} {item} {onSuccess} />
|
||||
|
||||
@@ -60,65 +60,67 @@
|
||||
</script>
|
||||
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<div class="flex flex-col h-full w-full overflow-hidden">
|
||||
<div class="relative min-h-[320px] max-h-[calc(100svh-340px)] flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<table class="w-full caption-bottom text-sm border-collapse">
|
||||
<thead class="sticky top-0 z-20 bg-background/95 shadow-sm backdrop-blur-md border-b">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
<tr>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<th class="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
</th>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
</thead>
|
||||
<tbody class="[&_tr:last-child]:border-0">
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
<tr class="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<td class="p-4 align-middle [&:has([role=checkbox])]:pr-0 whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</td>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</tr>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/each}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
<!-- Loading Trigger -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
<tr>
|
||||
<td colspan={columns.length} class="h-24 text-center p-0">
|
||||
<div bind:this={loadingTrigger} class="flex items-center justify-center h-full w-full">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
<div class="flex items-center justify-center gap-3 bg-muted/30 px-6 py-2 rounded-full border shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-foreground font-medium text-sm">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
<div class="text-muted-foreground text-sm flex items-center gap-2">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
|
||||
let { data, columns, loading = false, hasMore = false, loadMore }: Props = $props();
|
||||
|
||||
let scrollContainer: HTMLDivElement;
|
||||
let observer: IntersectionObserver;
|
||||
let scrollContainer = $state<HTMLDivElement>();
|
||||
let loadingTrigger = $state<HTMLDivElement>();
|
||||
|
||||
let options = $derived<TableOptions<TData>>({
|
||||
get data() {
|
||||
@@ -34,8 +34,7 @@
|
||||
onMount(() => {
|
||||
if (!loadMore) return;
|
||||
|
||||
// Create intersection observer for infinite scroll
|
||||
observer = new IntersectionObserver(
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const [entry] = entries;
|
||||
if (entry.isIntersecting && hasMore && !loading && loadMore) {
|
||||
@@ -48,26 +47,24 @@
|
||||
}
|
||||
);
|
||||
|
||||
// Observe the last row
|
||||
const lastRow = scrollContainer?.querySelector('tbody tr:last-child');
|
||||
if (lastRow) {
|
||||
observer.observe(lastRow);
|
||||
if (loadingTrigger) {
|
||||
observer.observe(loadingTrigger);
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer?.disconnect();
|
||||
observer.disconnect();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<div class="flex h-full w-full flex-col overflow-hidden">
|
||||
<div class="relative flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="bg-background">
|
||||
<Table.Header class="sticky top-0 z-20 border-b bg-background/95 shadow-sm backdrop-blur-md">
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
<Table.Head class="whitespace-nowrap">
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
@@ -83,7 +80,7 @@
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<Table.Cell class="whitespace-nowrap">
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
@@ -98,11 +95,24 @@
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
|
||||
{#if loading}
|
||||
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-12 text-center text-muted-foreground">
|
||||
Cargando...
|
||||
<Table.Cell colspan={columns.length} class="h-24 p-0 text-center">
|
||||
<div bind:this={loadingTrigger} class="flex h-full w-full items-center justify-center">
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-3 rounded-full border bg-muted/30 px-6 py-2 shadow-sm">
|
||||
<div class="h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent text-primary"></div>
|
||||
<span class="text-sm font-medium text-foreground">Cargando más registros...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
<span>Desplázate para cargar más</span>
|
||||
<span class="h-px w-8 bg-border"></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="sidebar-inset"
|
||||
class={cn(
|
||||
"bg-background relative flex w-full flex-1 flex-col",
|
||||
"bg-background relative flex min-h-0 w-full flex-1 flex-col overflow-hidden",
|
||||
"md:peer-data-[variant=inset]:m-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:peer-data-[state=collapsed]:ml-2 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow-sm",
|
||||
className
|
||||
)}
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
data-slot="sidebar-wrapper"
|
||||
style="--sidebar-width: {SIDEBAR_WIDTH}; --sidebar-width-icon: {SIDEBAR_WIDTH_ICON}; {style}"
|
||||
class={cn(
|
||||
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
|
||||
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex h-svh w-full overflow-hidden",
|
||||
className
|
||||
)}
|
||||
bind:this={ref}
|
||||
|
||||
@@ -4,19 +4,24 @@ import type { ShortcutDef } from '$lib/stores/shortcut-store';
|
||||
* Reusable shortcuts for simple reference data catalogs.
|
||||
*/
|
||||
export const obtenerAtajosReferenceDataSimple = (acciones: {
|
||||
manejarNuevo: () => void;
|
||||
manejarNuevo?: () => void;
|
||||
manejarActualizar: () => void;
|
||||
}): ShortcutDef[] => {
|
||||
return [
|
||||
{
|
||||
const atajos: ShortcutDef[] = [];
|
||||
|
||||
if (acciones.manejarNuevo) {
|
||||
atajos.push({
|
||||
key: 'Alt+Shift+N',
|
||||
description: 'Nuevo Registro',
|
||||
action: acciones.manejarNuevo
|
||||
},
|
||||
{
|
||||
key: 'Alt+Shift+R',
|
||||
description: 'Actualizar Lista',
|
||||
action: acciones.manejarActualizar
|
||||
}
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
atajos.push({
|
||||
key: 'Alt+Shift+R',
|
||||
description: 'Actualizar Lista',
|
||||
action: acciones.manejarActualizar
|
||||
});
|
||||
|
||||
return atajos;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user