Merge pull request 'fix/catalogos-generales' (#303) from fix/catalogos-generales into development
Reviewed-on: ADUANASOFT/anexo76#303
This commit is contained in:
@@ -43,7 +43,7 @@ export async function getClassificationConcepts(
|
||||
}
|
||||
|
||||
export async function getClassificationConcept(id: number, companyId: number): Promise<ApiResponse<ClassificationConcept>> {
|
||||
return await api.get(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`);
|
||||
return await api.get(`/v1/a76/classification-concepts/${id}?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
export async function createClassificationConcept(
|
||||
@@ -62,5 +62,5 @@ export async function updateClassificationConcept(
|
||||
}
|
||||
|
||||
export async function deleteClassificationConcept(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`);
|
||||
return await api.delete(`/v1/a76/classification-concepts/${id}?company_id=${companyId}`);
|
||||
}
|
||||
@@ -426,7 +426,7 @@ export async function getCompanies(
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/v1/a76/company/?${queryParams.toString()}`);
|
||||
return await api.get(`/v1/a76/company?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
export async function getCompany(id: number): Promise<ApiResponse<Company>> {
|
||||
@@ -434,7 +434,7 @@ export async function getCompany(id: number): Promise<ApiResponse<Company>> {
|
||||
}
|
||||
|
||||
export async function createCompany(data: CompanyCreate): Promise<ApiResponse<Company>> {
|
||||
return await api.post(`/v1/a76/company/`, data);
|
||||
return await api.post(`/v1/a76/company`, data);
|
||||
}
|
||||
|
||||
export async function updateCompany(id: number, data: CompanyUpdate): Promise<ApiResponse<Company>> {
|
||||
@@ -442,7 +442,7 @@ export async function updateCompany(id: number, data: CompanyUpdate): Promise<Ap
|
||||
}
|
||||
|
||||
export async function deleteCompany(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/company/${id}/`);
|
||||
return await api.delete(`/v1/a76/company/${id}`);
|
||||
}
|
||||
|
||||
export async function uploadCompanyLogo(id: number, file: File): Promise<ApiResponse<{ message: string; logo_path: string; company_id: number }>> {
|
||||
|
||||
@@ -62,7 +62,7 @@ export async function getConcepts(
|
||||
|
||||
|
||||
export async function getConcept(id: number, companyId: number): Promise<ApiResponse<Concept>> {
|
||||
return await api.get(`/v1/a76/concepts/${id}/?company_id=${companyId}`);
|
||||
return await api.get(`/v1/a76/concepts/${id}?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -77,5 +77,5 @@ export async function updateConcept(id: number, data: ConceptUpdate, companyId:
|
||||
|
||||
|
||||
export async function deleteConcept(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/concepts/${id}/?company_id=${companyId}`);
|
||||
return await api.delete(`/v1/a76/concepts/${id}?company_id=${companyId}`);
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export async function getCustomsBrokerConcepts(
|
||||
}
|
||||
|
||||
export async function getCustomsBrokerConcept(id: number, companyId: number): Promise<ApiResponse<CustomsBrokerConcept>> {
|
||||
return await api.get(`/v1/a76/customs-broker-concepts/${id}/?company_id=${companyId}`);
|
||||
return await api.get(`/v1/a76/customs-broker-concepts/${id}?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
export async function createCustomsBrokerConcept(data: CustomsBrokerConceptCreate, companyId: number): Promise<ApiResponse<CustomsBrokerConcept>> {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface ElectronicNotice {
|
||||
id: number;
|
||||
@@ -52,7 +51,7 @@ export async function getElectronicNotices(
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {},
|
||||
companyId?: number
|
||||
): Promise<ApiResponse<ElectronicNoticeListResponse>> {
|
||||
): Promise<ElectronicNoticeListResponse> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
@@ -62,7 +61,13 @@ export async function getElectronicNotices(
|
||||
params.append('company_id', companyId.toString());
|
||||
}
|
||||
|
||||
const response = await api.get(`/v1/a76/electronic-notices/?${params.toString()}`);
|
||||
const response = await api.get<ElectronicNoticeListResponse>(`/v1/a76/electronic-notices/?${params.toString()}`);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (!response.data) {
|
||||
throw new Error('No se pudieron obtener los avisos electrónicos');
|
||||
}
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -71,20 +76,41 @@ export async function getElectronicNotice(id: number, companyId?: number): Promi
|
||||
if (companyId) {
|
||||
params.append('company_id', companyId.toString());
|
||||
}
|
||||
const response = await api.get(`/v1/a76/electronic-notices/${id}?${params.toString()}`);
|
||||
const response = await api.get<ElectronicNotice>(`/v1/a76/electronic-notices/${id}?${params.toString()}`);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (!response.data) {
|
||||
throw new Error('No se pudo obtener el aviso electrónico');
|
||||
}
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createElectronicNotice(data: ElectronicNoticeCreate, companyId: number): Promise<ElectronicNotice> {
|
||||
const response = await api.post(`/v1/a76/electronic-notices/?company_id=${companyId}`, data);
|
||||
const response = await api.post<ElectronicNotice>(`/v1/a76/electronic-notices/?company_id=${companyId}`, data);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (!response.data) {
|
||||
throw new Error('No se pudo crear el aviso electrónico');
|
||||
}
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateElectronicNotice(id: number, data: ElectronicNoticeUpdate, companyId: number): Promise<ElectronicNotice> {
|
||||
const response = await api.put(`/v1/a76/electronic-notices/${id}?company_id=${companyId}`, data);
|
||||
const response = await api.put<ElectronicNotice>(`/v1/a76/electronic-notices/${id}/?company_id=${companyId}`, data);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (!response.data) {
|
||||
throw new Error('No se pudo actualizar el aviso electrónico');
|
||||
}
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteElectronicNotice(id: number, companyId: number): Promise<void> {
|
||||
await api.delete(`/v1/a76/electronic-notices/${id}?company_id=${companyId}`);
|
||||
const response = await api.delete(`/v1/a76/electronic-notices/${id}?company_id=${companyId}`);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ export async function createErrorClassification(data: ErrorClassificationCreate,
|
||||
|
||||
export async function updateErrorClassification(id: number, data: ErrorClassificationUpdate, companyId: number): Promise<ErrorClassification> {
|
||||
const params = new URLSearchParams({ company_id: companyId.toString() });
|
||||
const response = await api.put(`/v1/a76/error-catalogs/classifications/${id}?${params.toString()}`, data);
|
||||
const response = await api.put(`/v1/a76/error-catalogs/classifications/${id}/?${params.toString()}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ export async function createErrorCatalog(data: ErrorCatalogCreate, companyId: nu
|
||||
|
||||
export async function updateErrorCatalog(id: number, data: ErrorCatalogUpdate, companyId: number): Promise<ErrorCatalog> {
|
||||
const params = new URLSearchParams({ company_id: companyId.toString() });
|
||||
const response = await api.put(`/v1/a76/error-catalogs/${id}?${params.toString()}`, data);
|
||||
const response = await api.put(`/v1/a76/error-catalogs/${id}/?${params.toString()}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ export async function deleteIdentifier(
|
||||
id: number,
|
||||
companyId: number
|
||||
): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/identifiers/${id}/?company_id=${companyId}`);
|
||||
return await api.delete(`/v1/a76/identifiers/${id}?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,5 +122,5 @@ export async function deleteIdentifierDetail(
|
||||
id: number,
|
||||
companyId: number
|
||||
): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/identifiers/details/${id}/?company_id=${companyId}`);
|
||||
return await api.delete(`/v1/a76/identifiers/details/${id}?company_id=${companyId}`);
|
||||
}
|
||||
@@ -45,7 +45,7 @@ export async function getINPCs(
|
||||
}
|
||||
|
||||
export async function getINPC(id: number, companyId: number): Promise<ApiResponse<INPC>> {
|
||||
return await api.get(`/v1/a76/inpc/${id}/?company_id=${companyId}`);
|
||||
return await api.get(`/v1/a76/inpc/${id}?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
export async function createINPC(
|
||||
@@ -64,5 +64,5 @@ export async function updateINPC(
|
||||
}
|
||||
|
||||
export async function deleteINPC(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/inpc/${id}/?company_id=${companyId}`);
|
||||
return await api.delete(`/v1/a76/inpc/${id}?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ export async function getLegends(
|
||||
}
|
||||
|
||||
export async function getLegend(id: number, companyId: number): Promise<ApiResponse<Legend>> {
|
||||
return await api.get(`/v1/a76/legends/${id}/?company_id=${companyId}`);
|
||||
return await api.get(`/v1/a76/legends/${id}?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
export async function createLegend(
|
||||
@@ -61,5 +61,5 @@ export async function updateLegend(
|
||||
}
|
||||
|
||||
export async function deleteLegend(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/legends/${id}/?company_id=${companyId}`);
|
||||
return await api.delete(`/v1/a76/legends/${id}?company_id=${companyId}`);
|
||||
}
|
||||
@@ -75,7 +75,7 @@ export async function updateMultiCurrencyType(
|
||||
): Promise<ApiResponse<MultiCurrencyType>> {
|
||||
const params = new URLSearchParams({ company_id: companyId.toString() });
|
||||
return api.put<MultiCurrencyType>(
|
||||
`/v1/a76/multi-currency-types/${multiCurrencyTypeId}?${params.toString()}`,
|
||||
`/v1/a76/multi-currency-types/${multiCurrencyTypeId}/?${params.toString()}`,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export async function getPackages(
|
||||
|
||||
|
||||
export async function getPackage(id: number, companyId: number): Promise<ApiResponse<Package>> {
|
||||
return await api.get(`/v1/a76/packages/${id}/?company_id=${companyId}`);
|
||||
return await api.get(`/v1/a76/packages/${id}?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
export async function createPackage(
|
||||
|
||||
@@ -37,6 +37,10 @@ export interface PortUpdate {
|
||||
export interface PortListResponse {
|
||||
items: Port[];
|
||||
total: number;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
pageSize?: number;
|
||||
pages?: number;
|
||||
}
|
||||
|
||||
class PortsApi {
|
||||
|
||||
@@ -65,7 +65,7 @@ export async function createPrevalidator(data: PrevalidatorCreate, companyId: nu
|
||||
}
|
||||
|
||||
export async function updatePrevalidator(id: number, data: PrevalidatorUpdate, companyId: number): Promise<Prevalidator> {
|
||||
const response = await api.put(`/v1/a76/prevalidators/${id}?company_id=${companyId}`, data);
|
||||
const response = await api.put(`/v1/a76/prevalidators/${id}/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ export async function getSignatures(
|
||||
|
||||
|
||||
export async function getSignature(id: number, companyId: number): Promise<Signature> {
|
||||
const response = await api.get(`/v1/a76/signatures/${id}/?company_id=${companyId}`);
|
||||
const response = await api.get(`/v1/a76/signatures/${id}?company_id=${companyId}`);
|
||||
if (response.error) throw new Error(response.error);
|
||||
return response.data;
|
||||
}
|
||||
@@ -79,6 +79,6 @@ export async function updateSignature(
|
||||
}
|
||||
|
||||
export async function deleteSignature(id: number, companyId: number): Promise<void> {
|
||||
const response = await api.delete(`/v1/a76/signatures/${id}/?company_id=${companyId}`);
|
||||
const response = await api.delete(`/v1/a76/signatures/${id}?company_id=${companyId}`);
|
||||
if (response.error) throw new Error(response.error);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface UnitConversion {
|
||||
id: number;
|
||||
@@ -35,7 +34,7 @@ export async function getUnitConversions(
|
||||
pageSize: number = 50,
|
||||
companyId: number, // 👈 Obligatorio
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<UnitConversionListResponse>> {
|
||||
): Promise<UnitConversionListResponse> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
@@ -45,13 +44,16 @@ export async function getUnitConversions(
|
||||
|
||||
// Agregamos /v1 y prefijo.
|
||||
// NOTA: Revisa si en tu router definiste "unit_conversions" o "unit-conversions"
|
||||
const response = await api.get(`/v1/a76/unit-conversions/?${params.toString()}`);
|
||||
const response = await api.get<UnitConversionListResponse>(`/v1/a76/unit-conversions/?${params.toString()}`);
|
||||
if (response.error) throw new Error(response.error);
|
||||
if (!response.data) throw new Error('No se pudieron obtener las conversiones de unidades');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getUnitConversion(id: number, companyId: number): Promise<UnitConversion> {
|
||||
const response = await api.get(`/v1/a76/unit-conversions/${id}/?company_id=${companyId}`);
|
||||
const response = await api.get<UnitConversion>(`/v1/a76/unit-conversions/${id}?company_id=${companyId}`);
|
||||
if (response.error) throw new Error(response.error);
|
||||
if (!response.data) throw new Error('No se pudo obtener la conversión de unidades');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -59,8 +61,9 @@ export async function createUnitConversion(
|
||||
data: UnitConversionCreate,
|
||||
companyId: number
|
||||
): Promise<UnitConversion> {
|
||||
const response = await api.post(`/v1/a76/unit-conversions/?company_id=${companyId}`, data);
|
||||
const response = await api.post<UnitConversion>(`/v1/a76/unit-conversions/?company_id=${companyId}`, data);
|
||||
if (response.error) throw new Error(response.error);
|
||||
if (!response.data) throw new Error('No se pudo crear la conversión de unidades');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -70,8 +73,9 @@ export async function updateUnitConversion(
|
||||
data: UnitConversionUpdate,
|
||||
companyId: number
|
||||
): Promise<UnitConversion> {
|
||||
const response = await api.put(`/v1/a76/unit-conversions/${id}/?company_id=${companyId}`, data);
|
||||
const response = await api.put<UnitConversion>(`/v1/a76/unit-conversions/${id}/?company_id=${companyId}`, data);
|
||||
if (response.error) throw new Error(response.error);
|
||||
if (!response.data) throw new Error('No se pudo actualizar la conversión de unidades');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ export async function updateUnitOfMeasureACE(id: number, data: UnitOfMeasureACEU
|
||||
}
|
||||
|
||||
export async function deleteUnitOfMeasureACE(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/units-of-measure/ace/${id}/?company_id=${companyId}`);
|
||||
return await api.delete(`/v1/a76/units-of-measure/ace/${id}?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
// --- OMA ---
|
||||
@@ -106,7 +106,7 @@ export async function updateUnitOfMeasureOMA(id: number, data: UnitOfMeasureOMAU
|
||||
}
|
||||
|
||||
export async function deleteUnitOfMeasureOMA(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/units-of-measure/oma/${id}/?company_id=${companyId}`);
|
||||
return await api.delete(`/v1/a76/units-of-measure/oma/${id}?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
// --- American ---
|
||||
@@ -160,7 +160,7 @@ export async function updateUnitOfMeasureAmerican(id: number, data: UnitOfMeasur
|
||||
}
|
||||
|
||||
export async function deleteUnitOfMeasureAmerican(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/units-of-measure/american/${id}/?company_id=${companyId}`);
|
||||
return await api.delete(`/v1/a76/units-of-measure/american/${id}?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
// --- General ---
|
||||
@@ -271,7 +271,7 @@ export async function updateUnitOfMeasureCustoms(id: number, data: UnitOfMeasure
|
||||
}
|
||||
|
||||
export async function deleteUnitOfMeasureCustoms(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/units-of-measure/customs/${id}/?company_id=${companyId}`);
|
||||
return await api.delete(`/v1/a76/units-of-measure/customs/${id}?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
export interface UnitOfMeasure {
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { ExchangeRate } from '$lib/api/dashboard/a76/general_catalogs/exchange-rate';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<ExchangeRate>[] {
|
||||
export function createColumns(): ColumnDef<ExchangeRate>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'date',
|
||||
@@ -33,16 +31,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<ExchangeRate>[]
|
||||
accessorKey: 'foreign_currency',
|
||||
header: 'Moneda Extranjera',
|
||||
cell: ({ row }) => row.original.foreign_currency ?? 'N/A'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { ClassificationConcept } from '$lib/api/dashboard/a76/general_catalogs/classification-concepts';
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<ClassificationConcept>[] {
|
||||
export function createColumns(): ColumnDef<ClassificationConcept>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'classification',
|
||||
@@ -14,16 +12,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<ClassificationC
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { deleteClassificationConcept, type ClassificationConcept } from "$lib/api/dashboard/a76/general_catalogs/classification-concepts";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from "$lib/components/dashboard/general_catalogs/classification/create-edit-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: ClassificationConcept;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm(`¿Estás seguro de eliminar la clasificación "${item.classification}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteClassificationConcept(item.id, companyStore.activeCompany.id);
|
||||
|
||||
if (response.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Éxito
|
||||
if (response.status === 204 || response.status === 200 || !response.error) {
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={item}
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { Company } from '$lib/api/dashboard/a76/general_catalogs/company';
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Company>[] {
|
||||
export function createColumns(): ColumnDef<Company>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'name',
|
||||
@@ -24,15 +22,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Company>[] {
|
||||
accessorKey: 'program_number',
|
||||
header: 'No. Programa',
|
||||
cell: ({ row }) => row.original.program_number || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { Concept } from '$lib/api/dashboard/a76/general_catalogs/concepts';
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Concept>[] {
|
||||
export function createColumns(): ColumnDef<Concept>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
@@ -24,16 +22,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Concept>[] {
|
||||
accessorKey: 'section',
|
||||
header: 'Sección',
|
||||
cell: ({ row }) => row.original.section?.toString() || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { deleteConcept, type Concept } from "$lib/api/dashboard/a76/general_catalogs/concepts";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: Concept;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm(`¿Estás seguro de eliminar el concepto "${item.code}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteConcept(item.id, companyStore.activeCompany.id);
|
||||
|
||||
if (response.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Éxito
|
||||
if (response.status === 204 || response.status === 200 || !response.error) {
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={item}
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { CustomsBrokerConcept } from '$lib/api/dashboard/a76/general_catalogs/customs-broker-concepts';
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<CustomsBrokerConcept>[] {
|
||||
export function createColumns(): ColumnDef<CustomsBrokerConcept>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'broker_key',
|
||||
@@ -24,16 +22,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<CustomsBrokerCo
|
||||
accessorKey: 'priority',
|
||||
header: 'Prioridad',
|
||||
cell: ({ row }) => row.original.priority?.toString() || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { Doda } from '$lib/api/dashboard/a76/general_catalogs/doda';
|
||||
import { renderComponent, renderSnippet } from '$lib/components/ui/data-table';
|
||||
import { renderSnippet } from '$lib/components/ui/data-table';
|
||||
import { createRawSnippet } from 'svelte';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
function formatDate(date?: string | null): string {
|
||||
if (!date) return '-';
|
||||
@@ -18,7 +17,7 @@ function formatDate(date?: string | null): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Doda>[] {
|
||||
export function createColumns(): ColumnDef<Doda>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'id',
|
||||
@@ -119,16 +118,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Doda>[] {
|
||||
});
|
||||
return renderSnippet(statusSnippet, { status });
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { ElectronicNotice } from '$lib/api/dashboard/a76/general_catalogs/electronic-notices';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
import { Head } from '$lib/components/ui/table';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<ElectronicNotice>[] {
|
||||
export function createColumns(): ColumnDef<ElectronicNotice>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'notice_number',
|
||||
@@ -30,16 +28,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<ElectronicNotic
|
||||
accessorKey: 'status',
|
||||
header: 'Estatus',
|
||||
cell: ({ row }) => row.original.status || 'N/A'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Headers: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -3,6 +3,11 @@
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import {
|
||||
createElectronicNotice,
|
||||
updateElectronicNotice,
|
||||
type ElectronicNotice
|
||||
} from '$lib/api/dashboard/a76/general_catalogs/electronic-notices';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { obtenerAtajosFormularioAvisosElectrónicos } from '$lib/config/shortcuts/dashboard/general_catalogs/electronic_notices/edit';
|
||||
|
||||
@@ -89,10 +94,15 @@
|
||||
error = null;
|
||||
|
||||
try {
|
||||
let savedNotice: ElectronicNotice | undefined;
|
||||
if (isEdit && item) {
|
||||
await updateElectronicNotice(item.id, formData, companyId);
|
||||
savedNotice = await updateElectronicNotice(item.id, formData, companyId);
|
||||
} else {
|
||||
await createElectronicNotice(formData, companyId);
|
||||
savedNotice = await createElectronicNotice(formData, companyId);
|
||||
}
|
||||
|
||||
if (!savedNotice) {
|
||||
throw new Error('No se pudo guardar el aviso electrónico');
|
||||
}
|
||||
|
||||
open = false;
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { EllipsisVertical, Pencil, Trash2, LoaderCircle } from 'lucide-svelte';
|
||||
import type { ElectronicNotice } from '$lib/api/dashboard/a76/general_catalogs/electronic-notices';
|
||||
import { deleteElectronicNotice } from '$lib/api/dashboard/a76/general_catalogs/electronic-notices';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import CreateEditDialog from './create-edit-dialog.svelte';
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: ElectronicNotice;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let dialogOpen = $state(false);
|
||||
let selectedItem = $state<ElectronicNotice | null>(null);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm('¿Está seguro de eliminar este aviso electrónico?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
await deleteElectronicNotice(item.id, companyId);
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (err: any) {
|
||||
error = err.message || 'Error al eliminar el aviso electrónico';
|
||||
console.error('Error deleting electronic notice:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
selectedItem = item;
|
||||
dialogOpen = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={selectedItem}
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
@@ -1,17 +1,7 @@
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { Equivalency } from '$lib/api/dashboard/a76/general_catalogs/equivalencies';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import CatalogDataTableActions from './catalog-data-table-actions.svelte';
|
||||
|
||||
export function createCatalogColumns({
|
||||
onInsertItems,
|
||||
onEdit,
|
||||
onSuccess
|
||||
}: {
|
||||
onInsertItems: (equivalency: Equivalency) => void;
|
||||
onEdit: (equivalency: Equivalency) => void;
|
||||
onSuccess?: () => void;
|
||||
}): ColumnDef<Equivalency>[] {
|
||||
export function createCatalogColumns(): ColumnDef<Equivalency>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'identifier',
|
||||
@@ -22,18 +12,6 @@ export function createCatalogColumns({
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || ''
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(CatalogDataTableActions, {
|
||||
item: row.original,
|
||||
onInsertItems,
|
||||
onEdit,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { EllipsisVertical, Pencil, Trash2, LoaderCircle } from 'lucide-svelte';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import type { Equivalency } from '$lib/api/dashboard/a76/general_catalogs/equivalencies';
|
||||
import { deleteEquivalency } from '$lib/api/dashboard/a76/general_catalogs/equivalencies';
|
||||
|
||||
let {
|
||||
item,
|
||||
onInsertItems,
|
||||
onEdit,
|
||||
onSuccess
|
||||
}: {
|
||||
item: Equivalency;
|
||||
onInsertItems: (equivalency: Equivalency) => void;
|
||||
onEdit: (equivalency: Equivalency) => void;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm('¿Está seguro de eliminar esta equivalencia?')) return;
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) return;
|
||||
|
||||
loading = true;
|
||||
try {
|
||||
const response = await deleteEquivalency(item.id, companyId);
|
||||
if (response.error) throw new Error(response.error);
|
||||
onSuccess?.();
|
||||
} catch (err: any) {
|
||||
console.error('Error deleting equivalency:', err);
|
||||
alert(err?.message || 'Error al eliminar la equivalencia');
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => onInsertItems(item)}>
|
||||
<span>Insertar items</span>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => onEdit(item)}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-red-600" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Borrar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { EllipsisVertical, Pencil, Trash2, LoaderCircle } from 'lucide-svelte';
|
||||
import type { EquivalencyItem } from '$lib/api/dashboard/a76/general_catalogs/equivalencies';
|
||||
import { deleteEquivalencyItem } from '$lib/api/dashboard/a76/general_catalogs/equivalencies';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import CreateEditDialog from './create-edit-dialog.svelte';
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: EquivalencyItem;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let dialogOpen = $state(false);
|
||||
let selectedItem = $state<EquivalencyItem | null>(null);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm('¿Está seguro de eliminar esta equivalencia?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
const response = await deleteEquivalencyItem(item.id, companyId);
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (err: any) {
|
||||
error = err.message || 'Error al eliminar la equivalencia';
|
||||
console.error('Error deleting equivalency:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
selectedItem = item;
|
||||
dialogOpen = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={selectedItem}
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { ErrorCatalog } from '$lib/api/dashboard/a76/general_catalogs/error-catalogs';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<ErrorCatalog>[] {
|
||||
export function createColumns(): ColumnDef<ErrorCatalog>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
@@ -18,14 +16,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<ErrorCatalog>[]
|
||||
accessorKey: 'classification_id',
|
||||
header: 'Clasificación',
|
||||
cell: ({ row }) => row.original.classification_id ?? '—'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
})
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { EllipsisVertical, Pencil, Trash2, LoaderCircle } from 'lucide-svelte';
|
||||
import type { ErrorCatalog } from '$lib/api/dashboard/a76/general_catalogs/error-catalogs';
|
||||
import { deleteErrorCatalog } from '$lib/api/dashboard/a76/general_catalogs/error-catalogs';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import CreateEditDialog from './create-edit-dialog.svelte';
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: ErrorCatalog;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let dialogOpen = $state(false);
|
||||
let selectedItem = $state<ErrorCatalog | null>(null);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm('¿Está seguro de que desea eliminar este error?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
await deleteErrorCatalog(item.id, companyId);
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
} catch (err: any) {
|
||||
error = err.message || 'Error al eliminar el error';
|
||||
console.error('Error deleting:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
selectedItem = item;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
function handleDialogSuccess() {
|
||||
dialogOpen = false;
|
||||
selectedItem = null;
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical size={16} />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end" class="w-[160px]">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={selectedItem}
|
||||
onSuccess={handleDialogSuccess}
|
||||
/>
|
||||
@@ -1,10 +1,7 @@
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { Identifier } from '$lib/api/dashboard/a76/general_catalogs/identifiers';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
import { Header } from '$lib/components/ui/alert-dialog';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Identifier>[] {
|
||||
export function createColumns(): ColumnDef<Identifier>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
@@ -25,16 +22,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Identifier>[] {
|
||||
accessorKey: 'complement',
|
||||
header: 'Complemento',
|
||||
cell: ({ row }) => row.original.complement || 'N/A'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { EllipsisVertical, Pencil, Trash2, LoaderCircle } from 'lucide-svelte';
|
||||
import type { Identifier } from '$lib/api/dashboard/a76/general_catalogs/identifiers';
|
||||
import { deleteIdentifier } from '$lib/api/dashboard/a76/general_catalogs/identifiers';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import CreateEditDialog from './create-edit-dialog.svelte';
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: Identifier;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let dialogOpen = $state(false);
|
||||
let selectedItem = $state<Identifier | null>(null);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm('¿Está seguro de eliminar este identificador?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
await deleteIdentifier(item.id, companyId);
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (err: any) {
|
||||
error = err.message || 'Error al eliminar el identificador';
|
||||
console.error('Error deleting identifier:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
selectedItem = item;
|
||||
dialogOpen = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={selectedItem}
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { INPC } from '$lib/api/dashboard/a76/general_catalogs/inpc';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<INPC>[] {
|
||||
export function createColumns(): ColumnDef<INPC>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'year',
|
||||
@@ -19,16 +17,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<INPC>[] {
|
||||
accessorKey: 'value',
|
||||
header: 'Valor',
|
||||
cell: ({ row }) => row.original.value?.toString() || 'N/A'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { EllipsisVertical, Pencil, Trash2, LoaderCircle } from 'lucide-svelte';
|
||||
import type { INPC } from '$lib/api/dashboard/a76/general_catalogs/inpc';
|
||||
import { deleteINPC } from '$lib/api/dashboard/a76/general_catalogs/inpc';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import CreateEditDialog from './create-edit-dialog.svelte';
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: INPC;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let dialogOpen = $state(false);
|
||||
let selectedItem = $state<INPC | null>(null);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm('¿Está seguro de eliminar este registro de INPC?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
await deleteINPC(item.id, companyId);
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (err: any) {
|
||||
error = err.message || 'Error al eliminar el registro';
|
||||
console.error('Error deleting INPC:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
selectedItem = item;
|
||||
dialogOpen = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={selectedItem}
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { Legend } from '$lib/api/dashboard/a76/general_catalogs/legends';
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Legend>[] {
|
||||
export function createColumns(): ColumnDef<Legend>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
@@ -14,16 +12,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Legend>[] {
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { deleteLegend, type Legend } from "$lib/api/dashboard/a76/general_catalogs/legends";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateDialog from './create-edit-dialog.svelte';
|
||||
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: Legend;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm(`¿Estás seguro de eliminar la leyenda "${item.code}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteLegend(item.id, companyStore.activeCompany.id);
|
||||
|
||||
if (response.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Éxito
|
||||
if (response.status === 204 || response.status === 200 || !response.error) {
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateDialog
|
||||
bind:open={dialogOpen}
|
||||
item={item}
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
@@ -96,10 +96,15 @@
|
||||
publication_date: dateInt // Mandamos el INT que espera Python
|
||||
};
|
||||
|
||||
let response;
|
||||
if (isEdit && item) {
|
||||
await updateMultiCurrencyType(item.id, dataToSend, companyId);
|
||||
response = await updateMultiCurrencyType(item.id, dataToSend, companyId);
|
||||
} else {
|
||||
await createMultiCurrencyType(dataToSend, companyId);
|
||||
response = await createMultiCurrencyType(dataToSend, companyId);
|
||||
}
|
||||
|
||||
if (response?.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
open = false;
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { Prevalidator } from '$lib/api/dashboard/a76/general_catalogs/prevalidators';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
import { Header } from '$lib/components/ui/alert-dialog';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Prevalidator>[] {
|
||||
export function createColumns(): ColumnDef<Prevalidator>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
@@ -25,16 +22,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Prevalidator>[]
|
||||
accessorKey: 'patent_prevalidator',
|
||||
header: 'Patente',
|
||||
cell: ({ row }) => row.original.patent_prevalidator || 'N/A'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { EllipsisVertical, Pencil, Trash2, LoaderCircle } from 'lucide-svelte';
|
||||
import type { Prevalidator } from '$lib/api/dashboard/a76/general_catalogs/prevalidators';
|
||||
import { deletePrevalidator } from '$lib/api/dashboard/a76/general_catalogs/prevalidators';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import CreateEditDialog from './create-edit-dialog.svelte';
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: Prevalidator;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let dialogOpen = $state(false);
|
||||
let selectedItem = $state<Prevalidator | null>(null);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm('¿Está seguro de eliminar este prevalidador?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
await deletePrevalidator(item.id, companyId);
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (err: any) {
|
||||
error = err.message || 'Error al eliminar el prevalidador';
|
||||
console.error('Error deleting prevalidator:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
selectedItem = item;
|
||||
dialogOpen = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-red-600">
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={selectedItem}
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { Signature } from '$lib/api/dashboard/a76/general_catalogs/signatures';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Signature>[] {
|
||||
export function createColumns(): ColumnDef<Signature>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
@@ -19,14 +17,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Signature>[] {
|
||||
accessorKey: 'photo_path',
|
||||
header: 'Ruta Foto',
|
||||
cell: ({ row }) => row.original.photo_path ?? 'N/A'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
})
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { EllipsisVertical, Pencil, Trash2, LoaderCircle } from 'lucide-svelte';
|
||||
import type { Signature } from '$lib/api/dashboard/a76/general_catalogs/signatures';
|
||||
import { deleteSignature } from '$lib/api/dashboard/a76/general_catalogs/signatures';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import CreateEditDialog from './create-edit-dialog.svelte';
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: Signature;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let dialogOpen = $state(false);
|
||||
let selectedItem = $state<Signature | null>(null);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm('¿Está seguro de eliminar esta firma electrónica?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
await deleteSignature(item.id, companyId);
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (err: any) {
|
||||
error = err.message || 'Error al eliminar la firma';
|
||||
console.error('Error deleting signature:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
selectedItem = item;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
function handleDialogSuccess() {
|
||||
dialogOpen = false;
|
||||
selectedItem = null;
|
||||
if (onSuccess) onSuccess();
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical size={16} />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end" class="w-[160px]">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={selectedItem}
|
||||
onSuccess={handleDialogSuccess}
|
||||
/>
|
||||
@@ -1,10 +1,7 @@
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { UnitConversion } from '$lib/api/dashboard/a76/general_catalogs/unit-conversions';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
import { Header } from '$lib/components/ui/alert-dialog';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitConversion>[] {
|
||||
export function createColumns(): ColumnDef<UnitConversion>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'from_unit_code',
|
||||
@@ -17,15 +14,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitConversion>
|
||||
{
|
||||
accessorKey: 'conversion_factor',
|
||||
header: 'Factor de conversión'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) =>
|
||||
renderComponent(DataTableActions, {
|
||||
conversion: row.original,
|
||||
onSuccess
|
||||
})
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -15,11 +15,13 @@
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = null,
|
||||
conversion = null,
|
||||
mode = 'create',
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: UnitConversion | null;
|
||||
conversion?: UnitConversion | null;
|
||||
mode?: 'create' | 'edit';
|
||||
onSuccess?: () => void;
|
||||
@@ -27,7 +29,8 @@
|
||||
|
||||
// Atajos
|
||||
|
||||
const isEdit = $derived(mode === 'edit');
|
||||
const currentConversion = $derived(item ?? conversion);
|
||||
const isEdit = $derived(mode === 'edit' || !!currentConversion);
|
||||
const title = $derived(isEdit ? 'Editar Conversión' : 'Nueva Conversión');
|
||||
|
||||
let formData = $state({
|
||||
@@ -40,26 +43,15 @@
|
||||
let error = $state<string | null>(null);
|
||||
let showFromUomModal = $state(false);
|
||||
let showToUomModal = $state(false);
|
||||
let wasOpen = $state(false);
|
||||
|
||||
function resetForm() {
|
||||
formData = {
|
||||
from_unit_code: '',
|
||||
to_unit_code: '',
|
||||
conversion_factor: ''
|
||||
};
|
||||
error = null;
|
||||
loading = false;
|
||||
showFromUomModal = false;
|
||||
showToUomModal = false;
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (conversion) {
|
||||
if (!open) return;
|
||||
|
||||
if (currentConversion) {
|
||||
formData = {
|
||||
from_unit_code: conversion.from_unit_code || '',
|
||||
to_unit_code: conversion.to_unit_code || '',
|
||||
conversion_factor: conversion.conversion_factor.toString() || ''
|
||||
from_unit_code: currentConversion.from_unit_code || '',
|
||||
to_unit_code: currentConversion.to_unit_code || '',
|
||||
conversion_factor: currentConversion.conversion_factor.toString() || ''
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
@@ -68,14 +60,11 @@
|
||||
conversion_factor: ''
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
// Reset each time the dialog is opened in create mode
|
||||
if (open && !wasOpen && !isEdit) {
|
||||
resetForm();
|
||||
}
|
||||
wasOpen = open;
|
||||
error = null;
|
||||
loading = false;
|
||||
showFromUomModal = false;
|
||||
showToUomModal = false;
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
@@ -101,8 +90,8 @@
|
||||
throw new Error('El factor de conversión debe ser un número válido');
|
||||
}
|
||||
|
||||
if (isEdit && conversion) {
|
||||
await updateUnitConversion(conversion.id, dataToSend, companyId);
|
||||
if (isEdit && currentConversion) {
|
||||
await updateUnitConversion(currentConversion.id, dataToSend, companyId);
|
||||
} else {
|
||||
await createUnitConversion(dataToSend, companyId);
|
||||
}
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { deleteUnitConversion, type UnitConversion } from "$lib/api/dashboard/a76/general_catalogs/unit-conversions";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import { toast } from "svelte-sonner";
|
||||
|
||||
let {
|
||||
conversion,
|
||||
onSuccess
|
||||
}: {
|
||||
conversion: UnitConversion;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm(`¿Estás seguro de eliminar la conversión "${conversion.from_unit_code} → ${conversion.to_unit_code}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
toast.error('Selecciona una compañía', {
|
||||
description: 'No se puede eliminar sin una compañía activa.'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
await deleteUnitConversion(conversion.id, companyStore.activeCompany.id);
|
||||
toast.success('Conversión eliminada');
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
} catch (e) {
|
||||
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
|
||||
toast.error('No se pudo eliminar', { description: errorMsg });
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
conversion={conversion}
|
||||
mode="edit"
|
||||
{onSuccess}
|
||||
/>
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { UnitOfMeasureACE } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureACE>[] {
|
||||
export function createColumns(): ColumnDef<UnitOfMeasureACE>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
@@ -14,16 +12,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureAC
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { ColumnDef } from "@tanstack/table-core";
|
||||
import type { UnitOfMeasureAmerican } from "$lib/api/dashboard/a76/general_catalogs/units-of-measure";
|
||||
import { renderComponent } from "$lib/components/ui/data-table/index.js";
|
||||
import DataTableActions from "./data-table-actions.svelte";
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureAmerican>[] {
|
||||
export function createColumns(): ColumnDef<UnitOfMeasureAmerican>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "code",
|
||||
@@ -13,14 +11,5 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureAm
|
||||
accessorKey: "description",
|
||||
header: "Descripción",
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: "Acciones",
|
||||
cell: ({ row }) =>
|
||||
renderComponent(DataTableActions, {
|
||||
unit: row.original,
|
||||
onSuccess
|
||||
}),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
unit?: UnitOfMeasureAmerican;
|
||||
item?: UnitOfMeasureAmerican | null;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
let { open = $bindable(), unit, onSuccess }: Props = $props();
|
||||
let { open = $bindable(), item, onSuccess }: Props = $props();
|
||||
|
||||
// Atajos
|
||||
|
||||
@@ -29,41 +29,64 @@
|
||||
code: '',
|
||||
description: ''
|
||||
});
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (unit) {
|
||||
if (item) {
|
||||
formData = {
|
||||
code: unit.code,
|
||||
description: unit.description || ''
|
||||
code: item.code,
|
||||
description: item.description || ''
|
||||
};
|
||||
} else {
|
||||
formData = { code: '', description: '' };
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
const activeCompanyId = companyStore.activeCompany?.id;
|
||||
if (!activeCompanyId) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const activeCompanyId = companyStore.activeCompany?.id;
|
||||
if (!activeCompanyId) {
|
||||
throw new Error('No hay una compañía seleccionada');
|
||||
}
|
||||
|
||||
const data: UnitOfMeasureAmericanCreate | UnitOfMeasureAmericanUpdate = {
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
};
|
||||
let response;
|
||||
if (item) {
|
||||
response = await updateUnitOfMeasureAmerican(
|
||||
item.id,
|
||||
{
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
} satisfies UnitOfMeasureAmericanUpdate,
|
||||
activeCompanyId
|
||||
);
|
||||
} else {
|
||||
response = await createUnitOfMeasureAmerican(
|
||||
{
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
} satisfies UnitOfMeasureAmericanCreate,
|
||||
activeCompanyId
|
||||
);
|
||||
}
|
||||
|
||||
const response = unit
|
||||
? await updateUnitOfMeasureAmerican(unit.id, data, activeCompanyId)
|
||||
: await createUnitOfMeasureAmerican(data, activeCompanyId);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
} else {
|
||||
open = false;
|
||||
onSuccess?.();
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar la unidad';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -71,20 +94,25 @@
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[425px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{unit ? 'Editar' : 'Crear'} Unidad Americana</Dialog.Title>
|
||||
<Dialog.Title>{item ? 'Editar' : 'Crear'} Unidad Americana</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
<form onsubmit={handleSubmit} class="space-y-4">
|
||||
{#if error}
|
||||
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">{error}</div>
|
||||
{/if}
|
||||
<div class="space-y-2">
|
||||
<Label for="code">Código * (máx. 3 caracteres)</Label>
|
||||
<Input id="code" bind:value={formData.code} required maxlength="3" />
|
||||
<Input id="code" bind:value={formData.code} required maxlength={3} disabled={loading} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="description">Descripción (máx. 40 caracteres)</Label>
|
||||
<Input id="description" bind:value={formData.description} maxlength="40" />
|
||||
<Input id="description" bind:value={formData.description} maxlength={40} disabled={loading} />
|
||||
</div>
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => (open = false)}>Cancelar</Button>
|
||||
<Button type="submit">Guardar</Button>
|
||||
<Button type="button" variant="outline" onclick={() => (open = false)} disabled={loading}
|
||||
>Cancelar</Button
|
||||
>
|
||||
<Button type="submit" disabled={loading}>{loading ? 'Guardando...' : 'Guardar'}</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { ColumnDef } from "@tanstack/table-core";
|
||||
import type { UnitOfMeasureCustoms } from "$lib/api/dashboard/a76/general_catalogs/units-of-measure";
|
||||
import { renderComponent } from "$lib/components/ui/data-table/index.js";
|
||||
import DataTableActions from "./data-table-actions.svelte";
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureCustoms>[] {
|
||||
export function createColumns(): ColumnDef<UnitOfMeasureCustoms>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "code",
|
||||
@@ -17,14 +15,5 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureCu
|
||||
accessorKey: "a76_unit_code",
|
||||
header: "Código A76 / SCAII",
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: "Acciones",
|
||||
cell: ({ row }) =>
|
||||
renderComponent(DataTableActions, {
|
||||
unit: row.original,
|
||||
onSuccess
|
||||
}),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
unit?: UnitOfMeasureCustoms;
|
||||
item?: UnitOfMeasureCustoms | null;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
let { open = $bindable(), unit, onSuccess }: Props = $props();
|
||||
let { open = $bindable(), item, onSuccess }: Props = $props();
|
||||
|
||||
// Atajos
|
||||
|
||||
@@ -30,50 +30,64 @@
|
||||
description: '',
|
||||
a76_unit_code: ''
|
||||
});
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (unit) {
|
||||
if (item) {
|
||||
formData = {
|
||||
code: unit.code,
|
||||
description: unit.description || '',
|
||||
a76_unit_code: unit.a76_unit_code || ''
|
||||
code: item.code,
|
||||
description: item.description || '',
|
||||
a76_unit_code: item.a76_unit_code || ''
|
||||
};
|
||||
} else {
|
||||
formData = { code: '', description: '', a76_unit_code: '' };
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
const activeCompanyId = companyStore.activeCompany?.id;
|
||||
if (!activeCompanyId) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const activeCompanyId = companyStore.activeCompany?.id;
|
||||
if (!activeCompanyId) {
|
||||
throw new Error('No hay una compañía seleccionada');
|
||||
}
|
||||
|
||||
const response = unit
|
||||
? await updateUnitOfMeasureCustoms(unit.id, {
|
||||
code: formData.code,
|
||||
description: formData.description || null,
|
||||
a76_unit_code: formData.a76_unit_code || null
|
||||
} satisfies UnitOfMeasureCustomsUpdate,
|
||||
activeCompanyId
|
||||
)
|
||||
: await createUnitOfMeasureCustoms(
|
||||
{
|
||||
code: formData.code,
|
||||
description: formData.description || null,
|
||||
a76_unit_code: formData.a76_unit_code || null
|
||||
} satisfies UnitOfMeasureCustomsCreate,
|
||||
activeCompanyId
|
||||
);
|
||||
const response = item
|
||||
? await updateUnitOfMeasureCustoms(
|
||||
item.id,
|
||||
{
|
||||
code: formData.code,
|
||||
description: formData.description || null,
|
||||
a76_unit_code: formData.a76_unit_code || null
|
||||
} satisfies UnitOfMeasureCustomsUpdate,
|
||||
activeCompanyId
|
||||
)
|
||||
: await createUnitOfMeasureCustoms(
|
||||
{
|
||||
code: formData.code,
|
||||
description: formData.description || null,
|
||||
a76_unit_code: formData.a76_unit_code || null
|
||||
} satisfies UnitOfMeasureCustomsCreate,
|
||||
activeCompanyId
|
||||
);
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
} else {
|
||||
open = false;
|
||||
onSuccess?.();
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar la unidad';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -81,24 +95,29 @@
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[425px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{unit ? 'Editar' : 'Crear'} Unidad Aduanas MEX</Dialog.Title>
|
||||
<Dialog.Title>{item ? 'Editar' : 'Crear'} Unidad Aduanas MEX</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
<form onsubmit={handleSubmit} class="space-y-4">
|
||||
{#if error}
|
||||
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">{error}</div>
|
||||
{/if}
|
||||
<div class="space-y-2">
|
||||
<Label for="code">Código * (máx. 10 caracteres)</Label>
|
||||
<Input id="code" bind:value={formData.code} required maxlength={10} />
|
||||
<Input id="code" bind:value={formData.code} required maxlength={10} disabled={loading} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="description">Descripción (máx. 20 caracteres)</Label>
|
||||
<Input id="description" bind:value={formData.description} maxlength={20} />
|
||||
<Input id="description" bind:value={formData.description} maxlength={20} disabled={loading} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="a76_unit_code">Código A76 / SCAII</Label>
|
||||
<Input id="a76_unit_code" bind:value={formData.a76_unit_code} maxlength={5} />
|
||||
<Input id="a76_unit_code" bind:value={formData.a76_unit_code} maxlength={5} disabled={loading} />
|
||||
</div>
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => (open = false)}>Cancelar</Button>
|
||||
<Button type="submit">Guardar</Button>
|
||||
<Button type="button" variant="outline" onclick={() => (open = false)} disabled={loading}
|
||||
>Cancelar</Button
|
||||
>
|
||||
<Button type="submit" disabled={loading}>{loading ? 'Guardando...' : 'Guardar'}</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { UnitOfMeasureGeneral } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureGeneral>[] {
|
||||
export function createColumns(): ColumnDef<UnitOfMeasureGeneral>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
@@ -14,16 +12,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureGe
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: (info) => info.getValue() || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
unit: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
unit?: UnitOfMeasureGeneral;
|
||||
item?: UnitOfMeasureGeneral | null;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
let { open = $bindable(), unit, onSuccess }: Props = $props();
|
||||
let { open = $bindable(), item, onSuccess }: Props = $props();
|
||||
|
||||
// Atajos
|
||||
|
||||
@@ -29,41 +29,64 @@
|
||||
code: '',
|
||||
description: ''
|
||||
});
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (unit) {
|
||||
if (item) {
|
||||
formData = {
|
||||
code: unit.code,
|
||||
description: unit.description || ''
|
||||
code: item.code,
|
||||
description: item.description || ''
|
||||
};
|
||||
} else {
|
||||
formData = { code: '', description: '' };
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
const activeCompanyId = companyStore.activeCompany?.id;
|
||||
if (!activeCompanyId) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const activeCompanyId = companyStore.activeCompany?.id;
|
||||
if (!activeCompanyId) {
|
||||
throw new Error('No hay una compañía seleccionada');
|
||||
}
|
||||
|
||||
const data: UnitOfMeasureGeneralCreate | UnitOfMeasureGeneralUpdate = {
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
};
|
||||
let response;
|
||||
if (item) {
|
||||
response = await updateUnitOfMeasureGeneral(
|
||||
item.id,
|
||||
{
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
} satisfies UnitOfMeasureGeneralUpdate,
|
||||
activeCompanyId
|
||||
);
|
||||
} else {
|
||||
response = await createUnitOfMeasureGeneral(
|
||||
{
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
} satisfies UnitOfMeasureGeneralCreate,
|
||||
activeCompanyId
|
||||
);
|
||||
}
|
||||
|
||||
const response = unit
|
||||
? await updateUnitOfMeasureGeneral(unit.id, data, activeCompanyId)
|
||||
: await createUnitOfMeasureGeneral(data, activeCompanyId);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
} else {
|
||||
open = false;
|
||||
onSuccess?.();
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar la unidad';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -71,20 +94,25 @@
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[425px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{unit ? 'Editar' : 'Crear'} Unidad General</Dialog.Title>
|
||||
<Dialog.Title>{item ? 'Editar' : 'Crear'} Unidad General</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
<form onsubmit={handleSubmit} class="space-y-4">
|
||||
{#if error}
|
||||
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">{error}</div>
|
||||
{/if}
|
||||
<div class="space-y-2">
|
||||
<Label for="code">Código *</Label>
|
||||
<Input id="code" bind:value={formData.code} required />
|
||||
<Input id="code" bind:value={formData.code} required disabled={loading} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="description">Descripción</Label>
|
||||
<Input id="description" bind:value={formData.description} />
|
||||
<Input id="description" bind:value={formData.description} disabled={loading} />
|
||||
</div>
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => (open = false)}>Cancelar</Button>
|
||||
<Button type="submit">Guardar</Button>
|
||||
<Button type="button" variant="outline" onclick={() => (open = false)} disabled={loading}
|
||||
>Cancelar</Button
|
||||
>
|
||||
<Button type="submit" disabled={loading}>{loading ? 'Guardando...' : 'Guardar'}</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { ColumnDef } from "@tanstack/table-core";
|
||||
import type { UnitOfMeasureOMA } from "$lib/api/dashboard/a76/general_catalogs/units-of-measure";
|
||||
import { renderComponent } from "$lib/components/ui/data-table/index.js";
|
||||
import DataTableActions from "./data-table-actions.svelte";
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureOMA>[] {
|
||||
export function createColumns(): ColumnDef<UnitOfMeasureOMA>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "code",
|
||||
@@ -13,14 +11,5 @@ export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureOM
|
||||
accessorKey: "description",
|
||||
header: "Descripción",
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
header: "Acciones",
|
||||
cell: ({ row }) =>
|
||||
renderComponent(DataTableActions, {
|
||||
unit: row.original,
|
||||
onSuccess
|
||||
}),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
unit?: UnitOfMeasureOMA;
|
||||
item?: UnitOfMeasureOMA | null;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
let { open = $bindable(), unit, onSuccess }: Props = $props();
|
||||
let { open = $bindable(), item, onSuccess }: Props = $props();
|
||||
|
||||
// Atajos
|
||||
|
||||
@@ -29,41 +29,64 @@
|
||||
code: '',
|
||||
description: ''
|
||||
});
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (unit) {
|
||||
if (item) {
|
||||
formData = {
|
||||
code: unit.code,
|
||||
description: unit.description || ''
|
||||
code: item.code,
|
||||
description: item.description || ''
|
||||
};
|
||||
} else {
|
||||
formData = { code: '', description: '' };
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
const activeCompanyId = companyStore.activeCompany?.id;
|
||||
if (!activeCompanyId) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const activeCompanyId = companyStore.activeCompany?.id;
|
||||
if (!activeCompanyId) {
|
||||
throw new Error('No hay una compañía seleccionada');
|
||||
}
|
||||
|
||||
const data: UnitOfMeasureOMACreate | UnitOfMeasureOMAUpdate = {
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
};
|
||||
let response;
|
||||
if (item) {
|
||||
response = await updateUnitOfMeasureOMA(
|
||||
item.id,
|
||||
{
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
} satisfies UnitOfMeasureOMAUpdate,
|
||||
activeCompanyId
|
||||
);
|
||||
} else {
|
||||
response = await createUnitOfMeasureOMA(
|
||||
{
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
} satisfies UnitOfMeasureOMACreate,
|
||||
activeCompanyId
|
||||
);
|
||||
}
|
||||
|
||||
const response = unit
|
||||
? await updateUnitOfMeasureOMA(unit.id, data, activeCompanyId)
|
||||
: await createUnitOfMeasureOMA(data, activeCompanyId);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
} else {
|
||||
open = false;
|
||||
onSuccess?.();
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar la unidad';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -71,20 +94,25 @@
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[425px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{unit ? 'Editar' : 'Crear'} Unidad OMA</Dialog.Title>
|
||||
<Dialog.Title>{item ? 'Editar' : 'Crear'} Unidad OMA</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
<form onsubmit={handleSubmit} class="space-y-4">
|
||||
{#if error}
|
||||
<div class="rounded-md bg-destructive/15 p-3 text-sm text-destructive">{error}</div>
|
||||
{/if}
|
||||
<div class="space-y-2">
|
||||
<Label for="code">Código * (máx. 10 caracteres)</Label>
|
||||
<Input id="code" bind:value={formData.code} required maxlength="10" />
|
||||
<Input id="code" bind:value={formData.code} required maxlength={10} disabled={loading} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="description">Descripción (máx. 200 caracteres)</Label>
|
||||
<Input id="description" bind:value={formData.description} maxlength="200" />
|
||||
<Input id="description" bind:value={formData.description} maxlength={200} disabled={loading} />
|
||||
</div>
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => (open = false)}>Cancelar</Button>
|
||||
<Button type="submit">Guardar</Button>
|
||||
<Button type="button" variant="outline" onclick={() => (open = false)} disabled={loading}
|
||||
>Cancelar</Button
|
||||
>
|
||||
<Button type="submit" disabled={loading}>{loading ? 'Guardando...' : 'Guardar'}</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import type { Port } from '$lib/api/dashboard/a76/general_catalogs/ports';
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Port>[] {
|
||||
export function createColumns(): ColumnDef<Port>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'port_code',
|
||||
@@ -33,16 +31,6 @@ export function createColumns(onSuccess?: () => void): ColumnDef<Port>[] {
|
||||
if (type === 'BOTH') return 'Ambos';
|
||||
return type;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(mode === 'edit');
|
||||
const isEdit = $derived(mode === 'edit' || !!item);
|
||||
const title = $derived(isEdit ? 'Editar Puerto' : 'Nuevo Puerto');
|
||||
|
||||
// Atajos
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import type { Port } from "$lib/api/dashboard/a76/general_catalogs/ports";
|
||||
import { EllipsisVertical, Pencil, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
import DeleteDialog from "./delete-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: Port;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let editDialogOpen = $state(false);
|
||||
let deleteDialogOpen = $state(false);
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => editDialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Item onclick={() => deleteDialogOpen = true} class="text-destructive">
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={editDialogOpen}
|
||||
mode="edit"
|
||||
{item}
|
||||
{onSuccess}
|
||||
/>
|
||||
|
||||
<DeleteDialog
|
||||
bind:open={deleteDialogOpen}
|
||||
{item}
|
||||
{onSuccess}
|
||||
/>
|
||||
@@ -1,26 +1,12 @@
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { Seal } from '$lib/api/dashboard/a76/general_catalogs/seal';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Seal>[] {
|
||||
export function createColumns(): ColumnDef<Seal>[] {
|
||||
return [
|
||||
|
||||
{
|
||||
accessorKey: 'seal',
|
||||
header: 'Sello',
|
||||
cell: ({ row }) => row.original.seal,
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
meta: {
|
||||
class: 'w-[100px] text-right'
|
||||
},
|
||||
cell: ({ row }) => renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
})
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { EllipsisVertical, Pencil, Trash2, LoaderCircle } from 'lucide-svelte';
|
||||
import type { Seal } from '$lib/api/dashboard/a76/general_catalogs/seal';
|
||||
import { deleteSeal } from '$lib/api/dashboard/a76/general_catalogs/seal';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import CreateEditDialog from './create-edit-dialog.svelte';
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: Seal;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let dialogOpen = $state(false);
|
||||
let selectedItem = $state<Seal | null>(null);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm('¿Está seguro de que desea eliminar este sello?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
alert('No hay compañía seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
await deleteSeal(item.id, companyId);
|
||||
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
} catch (err: any) {
|
||||
error = err.message || 'Error al eliminar el sello';
|
||||
alert(`Error: ${error}`);
|
||||
console.error('Error deleting:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
selectedItem = item;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
function handleDialogSuccess() {
|
||||
dialogOpen = false;
|
||||
selectedItem = null;
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="relative size-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical size={16} />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end" class="w-[160px]">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleEdit}>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item onclick={handleDelete} class="text-destructive" disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={selectedItem}
|
||||
onSuccess={handleDialogSuccess}
|
||||
/>
|
||||
@@ -1,122 +0,0 @@
|
||||
<script lang="ts" generics="TData, TValue">
|
||||
import { onMount } from 'svelte';
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import {
|
||||
getCoreRowModel,
|
||||
type TableOptions
|
||||
} from '@tanstack/table-core';
|
||||
import { createSvelteTable, FlexRender } from '$lib/components/ui/data-table/index.js';
|
||||
import * as Table from '$lib/components/ui/table';
|
||||
|
||||
type Props = {
|
||||
data: TData[];
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
loading?: boolean;
|
||||
hasMore?: boolean;
|
||||
loadMore?: () => void;
|
||||
};
|
||||
|
||||
let { data, columns, loading = false, hasMore = false, loadMore }: Props = $props();
|
||||
|
||||
let scrollContainer = $state<HTMLDivElement>();
|
||||
let loadingTrigger = $state<HTMLDivElement>();
|
||||
|
||||
let options = $derived<TableOptions<TData>>({
|
||||
get data() {
|
||||
return data;
|
||||
},
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel()
|
||||
});
|
||||
|
||||
let table = $derived(createSvelteTable(options));
|
||||
|
||||
onMount(() => {
|
||||
if (!loadMore) return;
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const [entry] = entries;
|
||||
if (entry.isIntersecting && hasMore && !loading && loadMore) {
|
||||
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="relative flex-1 overflow-auto bg-card shadow-inner" bind:this={scrollContainer}>
|
||||
<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.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}
|
||||
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<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}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
</div>
|
||||
@@ -6,23 +6,31 @@
|
||||
import CreateDialog from '$lib/components/dashboard/general_catalogs/classification/create-edit-dialog.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaClasificacion } from '$lib/config/shortcuts/dashboard/general_catalogs/classification/list';
|
||||
import { getClassificationConcepts } from '$lib/api/dashboard/a76/general_catalogs/classification-concepts';
|
||||
import { getClassificationConcepts, deleteClassificationConcept, type ClassificationConcept } from '$lib/api/dashboard/a76/general_catalogs/classification-concepts';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
let editingItem = $state<ClassificationConcept | null>(null);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
|
||||
);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Clasificaciones',
|
||||
obtenerAtajosListaClasificacion({
|
||||
manejarNuevo: () => (dialogOpen = true),
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
dialogOpen = true;
|
||||
},
|
||||
manejarActualizar: handleSuccess
|
||||
})
|
||||
);
|
||||
@@ -32,7 +40,7 @@
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = $derived(createColumns(handleSuccess));
|
||||
const columns = createColumns();
|
||||
|
||||
let allItems = $state(data.classifications?.items || []);
|
||||
let currentPage = $state(data.classifications?.page || 1);
|
||||
@@ -148,8 +156,55 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
dialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: ClassificationConcept) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: ClassificationConcept) {
|
||||
editingItem = row;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar la clasificación "${selectedItem.classification}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteClassificationConcept(selectedItem.id, companyStore.activeCompany.id);
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
return;
|
||||
}
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar la clasificación';
|
||||
console.error('Error deleting classification:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -159,11 +214,13 @@
|
||||
<p class="text-muted-foreground">Gestión del catálogo de clasificaciones de conceptos</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2 h-4 w-4" />
|
||||
Actualizar
|
||||
</Button>
|
||||
<Button class="h-9" onclick={() => (dialogOpen = true)}>
|
||||
<Button
|
||||
class="h-9"
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
dialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Clasificación
|
||||
</Button>
|
||||
@@ -204,6 +261,10 @@
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
@@ -212,8 +273,41 @@
|
||||
<div class="flex-none text-sm text-muted-foreground">
|
||||
Mostrando {allItems.length} de {totalItems} registros
|
||||
</div>
|
||||
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="classification-concepts-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateDialog
|
||||
bind:open={dialogOpen}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -6,26 +6,29 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import * as m from '$lib/paraglide/messages.js';
|
||||
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaEmpresa } from '$lib/config/shortcuts/dashboard/general_catalogs/company_information/list';
|
||||
import { getCompanies } from '$lib/api/dashboard/a76/general_catalogs/company';
|
||||
import { getCompanies, deleteCompany, type Company } from '$lib/api/dashboard/a76/general_catalogs/company';
|
||||
|
||||
let { data } = $props();
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
|
||||
);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Empresas',
|
||||
obtenerAtajosListaEmpresa({
|
||||
manejarNuevo: () => goto('/dashboard/general_catalogs/company_information/edit'),
|
||||
manejarActualizar: () => goto($page.url, { invalidateAll: true })
|
||||
manejarActualizar: handleSuccess
|
||||
})
|
||||
);
|
||||
let dialogOpen = $state(false);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
|
||||
// Filtros
|
||||
@@ -130,8 +133,42 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: Company) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
goto(`/dashboard/general_catalogs/company_information/edit/${selectedItem.id}`);
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar la empresa "${selectedItem.name}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
await deleteCompany(selectedItem.id);
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar la empresa';
|
||||
console.error('Error deleting company:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -141,11 +178,7 @@
|
||||
<p class="text-muted-foreground">Gestión de información de empresas</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2 h-4 w-4" />
|
||||
Actualizar
|
||||
</Button>
|
||||
<Button class="h-9" href="/dashboard/general_catalogs/company_information/edit">
|
||||
<Button class="h-9" onclick={() => goto('/dashboard/general_catalogs/company_information/edit')}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Empresa
|
||||
</Button>
|
||||
@@ -182,17 +215,50 @@
|
||||
<div class="rounded-md border bg-background overflow-hidden">
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
columns={createColumns(handleSuccess)}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
onRowDoubleClick={(item) => goto(`/dashboard/general_catalogs/company_information/edit/${item.id}`)}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={(item) =>
|
||||
goto(`/dashboard/general_catalogs/company_information/edit/${item.id}`)}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">
|
||||
Mostrando {allItems.length} de {totalItems} registros
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
|
||||
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="company-info-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,26 +6,33 @@
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/concepts/create-edit-dialog.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import type { PageData } from './$types';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaConceptos } from '$lib/config/shortcuts/dashboard/general_catalogs/concepts/list';
|
||||
import { getConcepts } from '$lib/api/dashboard/a76/general_catalogs/concepts';
|
||||
import { getConcepts, deleteConcept, type Concept } from '$lib/api/dashboard/a76/general_catalogs/concepts';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let dialogOpen = $state(false);
|
||||
let editingItem = $state<Concept | null>(null);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
|
||||
);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Conceptos',
|
||||
obtenerAtajosListaConceptos({
|
||||
manejarNuevo: () => (createDialogOpen = true),
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
dialogOpen = true;
|
||||
},
|
||||
manejarActualizar: () => goto($page.url, { invalidateAll: true })
|
||||
})
|
||||
);
|
||||
@@ -139,8 +146,55 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
dialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: Concept) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: Concept) {
|
||||
editingItem = row;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar el concepto "${selectedItem.code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteConcept(selectedItem.id, companyStore.activeCompany.id);
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
return;
|
||||
}
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar el concepto';
|
||||
console.error('Error deleting concept:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -150,11 +204,13 @@
|
||||
<p class="text-muted-foreground">Define los conceptos que se usan para capturar y clasificar cargos en la pestaña Cuenta de Gastos y A.A. del pedimento.</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2 h-4 w-4" />
|
||||
Actualizar
|
||||
</Button>
|
||||
<Button class="h-9" onclick={() => (dialogOpen = true)}>
|
||||
<Button
|
||||
class="h-9"
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
dialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Concepto
|
||||
</Button>
|
||||
@@ -191,10 +247,14 @@
|
||||
<div class="rounded-md border bg-background overflow-hidden">
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
columns={createColumns(handleSuccess)}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
@@ -204,7 +264,38 @@
|
||||
Mostrando {allItems.length} de {totalItems} registros
|
||||
</div>
|
||||
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="concepts-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if dialogOpen}
|
||||
<CreateEditDialog bind:open={dialogOpen} onSuccess={handleSuccess} />
|
||||
<CreateEditDialog bind:open={dialogOpen} item={editingItem} onSuccess={handleSuccess} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
let searchConcept = $state($page.url.searchParams.get('concept') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = $derived(createColumns(handleSuccess));
|
||||
const columns = $derived(createColumns());
|
||||
|
||||
let allItems = $state(data.concepts?.items || []);
|
||||
let currentPage = $state(data.concepts?.page || 1);
|
||||
|
||||
@@ -50,8 +50,10 @@
|
||||
let error = $state<string | null>(data.error || null);
|
||||
|
||||
// Selection
|
||||
let selectedId = $state<number | null>(null);
|
||||
const selectedDoda = $derived(selectedId ? allItems.find((i) => i.id === selectedId) : null);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedDoda = $derived(
|
||||
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
|
||||
);
|
||||
|
||||
// Sincronizar con datos del servidor al cargar (primera carga)
|
||||
$effect(() => {
|
||||
@@ -144,7 +146,7 @@
|
||||
allItems = res.data.items;
|
||||
currentPage = 1;
|
||||
totalItems = res.data.total;
|
||||
selectedId = null;
|
||||
selectedIds = [];
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error reloading DODAs:', e);
|
||||
@@ -169,18 +171,19 @@
|
||||
}
|
||||
|
||||
function handleEdit() {
|
||||
if (selectedId) {
|
||||
goto(`/dashboard/general_catalogs/doda/edit/${selectedId}`);
|
||||
if (selectedDoda) {
|
||||
goto(`/dashboard/general_catalogs/doda/edit/${selectedDoda.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete() {
|
||||
if (!selectedId || !companyStore.activeCompany) return;
|
||||
if (!selectedDoda || !companyStore.activeCompany) return;
|
||||
|
||||
if (confirm('¿Estás seguro de eliminar este DODA?')) {
|
||||
try {
|
||||
await deleteDoda(selectedId, companyStore.activeCompany.id);
|
||||
await deleteDoda(selectedDoda.id, companyStore.activeCompany.id);
|
||||
toast.success('DODA eliminado correctamente');
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
} catch (e) {
|
||||
toast.error('Error al eliminar DODA');
|
||||
@@ -188,8 +191,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
function handleRowClick(doda: Doda) {
|
||||
selectedId = selectedId === doda.id ? null : doda.id;
|
||||
function handleRowClick(row: Doda) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -200,10 +204,6 @@
|
||||
<p class="text-muted-foreground">Gestiona tus Documentos de Operación Aduanera (DODA)</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button variant="outline" size="sm" class="h-9" onclick={reloadData} disabled={loading}>
|
||||
<RefreshCw class="mr-2 h-4 w-4 {loading ? 'animate-spin' : ''}" />
|
||||
Actualizar
|
||||
</Button>
|
||||
<Button class="h-9" onclick={handleCreateClick}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo DODA
|
||||
@@ -270,11 +270,11 @@
|
||||
<div class="rounded-md border bg-background">
|
||||
<DataTable
|
||||
data={allItems}
|
||||
columns={createColumns(reloadData)}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedId}
|
||||
selectedId={selectedIds.length === 1 ? selectedIds[0] : null}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={(item) => goto(`/dashboard/general_catalogs/doda/edit/${item.id}`)}
|
||||
/>
|
||||
@@ -292,42 +292,35 @@
|
||||
|
||||
<!-- Footer fijo de acciones (estilo Facturas) -->
|
||||
<div
|
||||
id="doda-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-sm text-muted-foreground">
|
||||
{#if selectedDoda}
|
||||
Seleccionado: <span class="font-medium text-foreground"
|
||||
>{selectedDoda.integration_number || 'S/N'}</span
|
||||
>
|
||||
{:else}
|
||||
Selecciona un registro para ver acciones
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<Button variant="outline" size="sm" onclick={reloadData} disabled={loading}>
|
||||
<RefreshCw class="mr-2 h-4 w-4 {loading ? 'animate-spin' : ''}" />
|
||||
Actualizar
|
||||
</Button>
|
||||
|
||||
<Button variant="outline" size="sm" onclick={handleEdit} disabled={!selectedId}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</Button>
|
||||
|
||||
<Button variant="destructive" size="sm" onclick={handleDelete} disabled={!selectedId}>
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
Eliminar
|
||||
</Button>
|
||||
|
||||
<Separator orientation="vertical" class="mx-1 h-8" />
|
||||
|
||||
<Button variant="secondary" size="sm" disabled={!selectedId}>
|
||||
<Printer class="mr-2 h-4 w-4" />
|
||||
Imprimir
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEdit}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDelete}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
<Separator orientation="vertical" class="mx-1 h-8" />
|
||||
<Button variant="secondary" size="sm" disabled={selectedIds.length !== 1}>
|
||||
<Printer size={16} class="mr-2" />
|
||||
Imprimir
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,21 +8,25 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaAvisosElectrónicos } from '$lib/config/shortcuts/dashboard/general_catalogs/electronic_notices/list';
|
||||
import { getElectronicNotices } from '$lib/api/dashboard/a76/general_catalogs/electronic-notices';
|
||||
import { getElectronicNotices, deleteElectronicNotice, type ElectronicNotice } from '$lib/api/dashboard/a76/general_catalogs/electronic-notices';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
let editingItem = $state<ElectronicNotice | null>(null);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Avisos Electrónicos',
|
||||
obtenerAtajosListaAvisosElectrónicos({
|
||||
manejarNuevo: () => (dialogOpen = true),
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
dialogOpen = true;
|
||||
},
|
||||
manejarRefrescar: handleSuccess
|
||||
})
|
||||
);
|
||||
@@ -32,12 +36,18 @@
|
||||
let searchPedimento = $state($page.url.searchParams.get('pedimento') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
let allItems = $state(data.notices?.items || []);
|
||||
let allItems = $state<ElectronicNotice[]>(data.notices?.items || []);
|
||||
let currentPage = $state(data.notices?.page || 1);
|
||||
let pageSize = $state(data.notices?.page_size || 50);
|
||||
let totalItems = $state(data.notices?.total || 0);
|
||||
let loading = $state(false);
|
||||
let hasMore = $derived(allItems.length < totalItems);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1
|
||||
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
|
||||
: null
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
if (data.notices) {
|
||||
@@ -146,8 +156,50 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
dialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: ElectronicNotice) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: ElectronicNotice) {
|
||||
editingItem = row;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar el aviso "${selectedItem.notice_number}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
await deleteElectronicNotice(selectedItem.id, companyStore.activeCompany.id);
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar el aviso';
|
||||
console.error('Error deleting notice:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -156,7 +208,7 @@
|
||||
<h1 class="text-2xl font-bold tracking-tight">Avisos Electrónicos</h1>
|
||||
<p class="text-muted-foreground">Gestión del catálogo de avisos electrónicos</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (dialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nuevo Aviso</Button></div>
|
||||
<div class="flex items-center gap-3"><Button class="h-9" onclick={() => { editingItem = null; dialogOpen = true; }}><Plus class="mr-2 h-4 w-4" />Nuevo Aviso</Button></div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
@@ -167,10 +219,41 @@
|
||||
|
||||
<Card.Root class="border bg-background flex flex-col">
|
||||
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Avisos</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="No. aviso" bind:value={searchNotice} oninput={handleSearch} class="h-9 w-40 bg-card lg:w-52" /><Input placeholder="Pedimento" bind:value={searchPedimento} oninput={handleSearch} class="h-9 w-40 bg-card lg:w-52" /></div></div></Card.Header>
|
||||
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createColumns(handleSuccess)} {loading} {hasMore} {loadMore} /></div></Card.Content>
|
||||
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createColumns()} {loading} {hasMore} {loadMore} {selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} onRowClick={handleRowClick} onRowDoubleClick={handleRowDoubleClick} /></div></Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
|
||||
|
||||
<CreateEditDialog bind:open={dialogOpen} onSuccess={handleSuccess} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="electronic-notices-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog bind:open={dialogOpen} item={editingItem} onSuccess={handleSuccess} />
|
||||
</div>
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaEquivalencias } from '$lib/config/shortcuts/dashboard/general_catalogs/equivalencies/list';
|
||||
import DataEquivalenciesDialog from '$lib/components/dashboard/general_catalogs/equivalencies/data-equivalencies-dialog.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { getEquivalencies } from '$lib/api/dashboard/a76/general_catalogs/equivalencies';
|
||||
import { getEquivalencies, deleteEquivalency } from '$lib/api/dashboard/a76/general_catalogs/equivalencies';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
@@ -22,6 +22,11 @@
|
||||
let dataDialogOpen = $state(false);
|
||||
let selectedEquivalency = $state<Equivalency | null>(null);
|
||||
let dataMode = $state<'create' | 'edit'>('edit');
|
||||
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const singleSelected = $derived(
|
||||
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
|
||||
);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
@@ -148,20 +153,51 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
dataDialogOpen = false;
|
||||
selectedEquivalency = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleOpenInsertItems(equivalency: Equivalency) {
|
||||
selectedEquivalency = equivalency;
|
||||
function handleRowClick(row: Equivalency) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: Equivalency) {
|
||||
selectedEquivalency = row;
|
||||
dataMode = 'edit';
|
||||
dataDialogOpen = true;
|
||||
}
|
||||
|
||||
function handleOpenEditCatalog(equivalency: Equivalency) {
|
||||
selectedEquivalency = equivalency;
|
||||
function handleEditSelected() {
|
||||
if (!singleSelected) return;
|
||||
selectedEquivalency = singleSelected;
|
||||
dataMode = 'edit';
|
||||
dataDialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!singleSelected || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm('¿Estás seguro de eliminar la equivalencia seleccionada?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
await deleteEquivalency(singleSelected.id, companyStore.activeCompany.id);
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar la equivalencia';
|
||||
console.error('Error deleting equivalency:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -170,7 +206,7 @@
|
||||
<h1 class="text-2xl font-bold tracking-tight">Equivalencias</h1>
|
||||
<p class="text-muted-foreground">Catálogo de equivalencias</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9"
|
||||
<div class="flex items-center gap-3"><Button class="h-9"
|
||||
onclick={() => {
|
||||
selectedEquivalency = null;
|
||||
dataMode = 'create';
|
||||
@@ -190,11 +226,42 @@
|
||||
|
||||
<Card.Root class="border bg-background flex flex-col">
|
||||
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Equivalencias</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Código origen" bind:value={searchFrom} oninput={handleSearch} class="h-9 w-40 bg-card lg:w-52" /></div></div></Card.Header>
|
||||
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createCatalogColumns({ onInsertItems: handleOpenInsertItems, onEdit: handleOpenEditCatalog, onSuccess: handleSuccess })} {loading} {hasMore} {loadMore} /></div></Card.Content>
|
||||
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createCatalogColumns()} {loading} {hasMore} {loadMore} {selectedIds} onSelectedIdsChange={(ids) => (selectedIds = ids)} onRowClick={handleRowClick} onRowDoubleClick={handleRowDoubleClick} /></div></Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
|
||||
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="equivalencies-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DataEquivalenciesDialog
|
||||
bind:open={dataDialogOpen}
|
||||
equivalency={selectedEquivalency}
|
||||
|
||||
@@ -8,22 +8,30 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaErrores } from '$lib/config/shortcuts/dashboard/general_catalogs/error_catalogs/list';
|
||||
import type { PageData } from './$types';
|
||||
import { getErrorCatalogs } from '$lib/api/dashboard/a76/general_catalogs/error-catalogs';
|
||||
import { getErrorCatalogs, deleteErrorCatalog, type ErrorCatalog } from '$lib/api/dashboard/a76/general_catalogs/error-catalogs';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
let editingItem = $state<ErrorCatalog | null>(null);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
|
||||
);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Errores',
|
||||
obtenerAtajosListaErrores({
|
||||
manejarNuevo: () => (dialogOpen = true),
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
dialogOpen = true;
|
||||
},
|
||||
manejarActualizar: handleSuccess
|
||||
})
|
||||
);
|
||||
@@ -147,8 +155,50 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
dialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: ErrorCatalog) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: ErrorCatalog) {
|
||||
editingItem = row;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar el error "${selectedItem.code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
await deleteErrorCatalog(selectedItem.id, companyStore.activeCompany.id);
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar el error';
|
||||
console.error('Error deleting error catalog:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -157,7 +207,18 @@
|
||||
<h1 class="text-2xl font-bold tracking-tight">Errores de Facturación</h1>
|
||||
<p class="text-muted-foreground">Gestión del catálogo de errores de facturación</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (dialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nuevo Error</Button></div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button
|
||||
class="h-9"
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
dialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Error
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
@@ -168,10 +229,59 @@
|
||||
|
||||
<Card.Root class="border bg-background flex flex-col">
|
||||
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Errores</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Código" bind:value={searchCode} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /><Input placeholder="Descripción" bind:value={searchDesc} oninput={handleSearch} class="h-9 w-44 bg-card lg:w-64" /></div></div></Card.Header>
|
||||
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createColumns(handleSuccess)} {loading} {hasMore} {loadMore} /></div></Card.Content>
|
||||
<Card.Content class="p-0">
|
||||
<div class="rounded-md border bg-background overflow-hidden">
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
|
||||
|
||||
<CreateDialog bind:open={dialogOpen} onSuccess={handleSuccess} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="error-catalogs-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateDialog
|
||||
bind:open={dialogOpen}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,23 +8,31 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaTiposCambio } from '$lib/config/shortcuts/dashboard/general_catalogs/exchange_rate/list';
|
||||
import type { PageData } from './$types';
|
||||
import { getExchangeRates } from '$lib/api/dashboard/a76/general_catalogs/exchange-rate';
|
||||
import { getExchangeRates, deleteExchangeRate, type ExchangeRate } from '$lib/api/dashboard/a76/general_catalogs/exchange-rate';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let editingItem = $state<ExchangeRate | null>(null);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
|
||||
);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Tipos de Cambio',
|
||||
obtenerAtajosListaTiposCambio({
|
||||
manejarNuevo: () => (createDialogOpen = true),
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
},
|
||||
manejarActualizar: handleSuccess
|
||||
})
|
||||
);
|
||||
@@ -136,8 +144,50 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
createDialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: ExchangeRate) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: ExchangeRate) {
|
||||
editingItem = row;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar este tipo de cambio?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
await deleteExchangeRate(selectedItem.id, companyStore.activeCompany.id);
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar el tipo de cambio';
|
||||
console.error('Error deleting exchange rate:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -146,7 +196,18 @@
|
||||
<h1 class="text-2xl font-bold tracking-tight">Tipos de Cambio</h1>
|
||||
<p class="text-muted-foreground">Catálogo de tipos de cambio</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (createDialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nuevo Tipo de Cambio</Button></div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button
|
||||
class="h-9"
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Tipo de Cambio
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
@@ -157,10 +218,59 @@
|
||||
|
||||
<Card.Root class="border bg-background flex flex-col">
|
||||
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Tipos de Cambio</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Moneda" bind:value={searchCurrency} oninput={handleSearch} class="h-9 w-40 bg-card lg:w-52" /></div></div></Card.Header>
|
||||
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createColumns(handleSuccess)} {loading} {hasMore} {loadMore} /></div></Card.Content>
|
||||
<Card.Content class="p-0">
|
||||
<div class="rounded-md border bg-background overflow-hidden">
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="exchange-rate-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,23 +8,31 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaIdentificadores } from '$lib/config/shortcuts/dashboard/general_catalogs/identifiers/list';
|
||||
import { getIdentifiers } from '$lib/api/dashboard/a76/general_catalogs/identifiers';
|
||||
import { getIdentifiers, deleteIdentifier, type Identifier } from '$lib/api/dashboard/a76/general_catalogs/identifiers';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let editingItem = $state<Identifier | null>(null);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
|
||||
);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Identificadores',
|
||||
obtenerAtajosListaIdentificadores({
|
||||
manejarNuevo: () => (createDialogOpen = true),
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
},
|
||||
manejarRefrescar: handleSuccess
|
||||
})
|
||||
);
|
||||
@@ -138,8 +146,50 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
createDialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: Identifier) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: Identifier) {
|
||||
editingItem = row;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar el identificador "${selectedItem.code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
await deleteIdentifier(selectedItem.id, companyStore.activeCompany.id);
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar el identificador';
|
||||
console.error('Error deleting identifier:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -149,11 +199,13 @@
|
||||
<p class="text-muted-foreground">Catálogo de identificadores</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2 h-4 w-4" />
|
||||
Actualizar
|
||||
</Button>
|
||||
<Button class="h-9" onclick={() => (createDialogOpen = true)}>
|
||||
<Button
|
||||
class="h-9"
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Identificador
|
||||
</Button>
|
||||
@@ -190,10 +242,14 @@
|
||||
<div class="rounded-md border bg-background overflow-hidden">
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
columns={createColumns(handleSuccess)}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
@@ -203,5 +259,40 @@
|
||||
Mostrando {allItems.length} de {totalItems} registros
|
||||
</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="identifiers-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,24 +8,32 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaInpc } from '$lib/config/shortcuts/dashboard/general_catalogs/inpc/list';
|
||||
import { getINPCs } from '$lib/api/dashboard/a76/general_catalogs/inpc';
|
||||
import { getINPCs, deleteINPC, type INPC } from '$lib/api/dashboard/a76/general_catalogs/inpc';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let editingItem = $state<INPC | null>(null);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
|
||||
);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista INPC',
|
||||
obtenerAtajosListaInpc({
|
||||
manejarNuevo: () => (createDialogOpen = true),
|
||||
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
},
|
||||
manejarActualizar: handleSuccess
|
||||
})
|
||||
);
|
||||
|
||||
@@ -139,8 +147,55 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
createDialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: INPC) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: INPC) {
|
||||
editingItem = row;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar el INPC del periodo ${selectedItem.year}-${selectedItem.month}?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteINPC(selectedItem.id, companyStore.activeCompany.id);
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
return;
|
||||
}
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar el INPC';
|
||||
console.error('Error deleting INPC:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -150,11 +205,12 @@
|
||||
<p class="text-muted-foreground">Índice Nacional de Precios al Consumidor</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button variant="outline" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2 h-4 w-4" />
|
||||
Actualizar
|
||||
</Button>
|
||||
<Button onclick={() => (createDialogOpen = true)}>
|
||||
<Button
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo INPC
|
||||
</Button>
|
||||
@@ -191,10 +247,14 @@
|
||||
<div class="flex h-full min-h-0 rounded-md border bg-background overflow-hidden flex-col">
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
columns={createColumns(handleSuccess)}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
@@ -204,5 +264,40 @@
|
||||
Mostrando {allItems.length} de {totalItems} registros
|
||||
</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="inpc-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -6,24 +6,28 @@
|
||||
import CreateDialog from '$lib/components/dashboard/general_catalogs/legends/create-edit-dialog.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaLeyendas } from '$lib/config/shortcuts/dashboard/general_catalogs/legends/list';
|
||||
import { getLegends } from '$lib/api/dashboard/a76/general_catalogs/legends';
|
||||
import { getLegends, deleteLegend, type Legend } from '$lib/api/dashboard/a76/general_catalogs/legends';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
let editingItem = $state<Legend | null>(null);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Leyendas',
|
||||
obtenerAtajosListaLeyendas({
|
||||
manejarNuevo: () => (dialogOpen = true),
|
||||
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
dialogOpen = true;
|
||||
},
|
||||
manejarActualizar: handleSuccess
|
||||
})
|
||||
);
|
||||
|
||||
@@ -32,14 +36,20 @@
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = $derived(createColumns(handleSuccess));
|
||||
|
||||
let allItems = $state(data.legends?.items || []);
|
||||
|
||||
let allItems = $state<Legend[]>(data.legends?.items || []);
|
||||
let currentPage = $state(data.legends?.page || 1);
|
||||
let pageSize = $state(data.legends?.page_size || 50);
|
||||
let totalItems = $state(data.legends?.total || 0);
|
||||
let loading = $state(false);
|
||||
let hasMore = $derived(allItems.length < totalItems);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1
|
||||
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
|
||||
: null
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
if (data.legends) {
|
||||
@@ -63,6 +73,9 @@
|
||||
if (searchDesc) filters.description = searchDesc;
|
||||
|
||||
const response = await getLegends(1, pageSize, companyStore.activeCompany.id, filters);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data) {
|
||||
allItems = response.data.items;
|
||||
currentPage = 1;
|
||||
@@ -101,6 +114,9 @@
|
||||
companyStore.activeCompany.id,
|
||||
filters
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data?.items) {
|
||||
allItems = [...allItems, ...response.data.items];
|
||||
currentPage += 1;
|
||||
@@ -124,6 +140,9 @@
|
||||
if (searchDesc) filters.description = searchDesc;
|
||||
|
||||
const response = await getLegends(1, pageSize, companyStore.activeCompany.id, filters);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data) {
|
||||
allItems = response.data.items;
|
||||
currentPage = 1;
|
||||
@@ -138,8 +157,53 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
dialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: Legend) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: Legend) {
|
||||
editingItem = row;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar la leyenda "${selectedItem.code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteLegend(selectedItem.id, companyStore.activeCompany.id);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar la leyenda';
|
||||
console.error('Error deleting legend:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -149,11 +213,13 @@
|
||||
<p class="text-muted-foreground">Gestión del catálogo de leyendas fijas</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2 h-4 w-4" />
|
||||
Actualizar
|
||||
</Button>
|
||||
<Button class="h-9" onclick={() => (dialogOpen = true)}>
|
||||
<Button
|
||||
class="h-9"
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
dialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Leyenda
|
||||
</Button>
|
||||
@@ -190,10 +256,14 @@
|
||||
<div class="flex h-full min-h-0 rounded-md border bg-background overflow-hidden flex-col">
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
{columns}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
@@ -203,5 +273,40 @@
|
||||
Mostrando {allItems.length} de {totalItems} registros
|
||||
</div>
|
||||
|
||||
<CreateDialog bind:open={dialogOpen} onSuccess={handleSuccess} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="legends-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateDialog
|
||||
bind:open={dialogOpen}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -6,27 +6,32 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaMonedas } from '$lib/config/shortcuts/dashboard/general_catalogs/multi_currency_types/list';
|
||||
import {
|
||||
getMultiCurrencyTypes,
|
||||
deleteMultiCurrencyType,
|
||||
type MultiCurrencyType
|
||||
} from '$lib/api/dashboard/a76/general_catalogs/multi-currency-types';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
let editingItem = $state<MultiCurrencyType | null>(null);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Monedas',
|
||||
obtenerAtajosListaMonedas({
|
||||
manejarNuevo: () => (dialogOpen = true),
|
||||
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
dialogOpen = true;
|
||||
},
|
||||
manejarActualizar: handleSuccess
|
||||
})
|
||||
);
|
||||
|
||||
@@ -58,12 +63,18 @@
|
||||
}
|
||||
];
|
||||
|
||||
let allItems = $state(data.types?.items || []);
|
||||
let allItems = $state<MultiCurrencyType[]>(data.types?.items || []);
|
||||
let currentPage = $state(data.types?.page || 1);
|
||||
let pageSize = $state(data.types?.page_size || 50);
|
||||
let totalItems = $state(data.types?.total || 0);
|
||||
let loading = $state(false);
|
||||
let hasMore = $derived(allItems.length < totalItems);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1
|
||||
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
|
||||
: null
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
if (data.types) {
|
||||
@@ -91,6 +102,9 @@
|
||||
country_key: searchCountry || undefined
|
||||
}
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data?.items) {
|
||||
allItems = response.data.items;
|
||||
currentPage = response.data.page || 1;
|
||||
@@ -128,6 +142,9 @@
|
||||
country_key: searchCountry || undefined
|
||||
}
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data?.items) {
|
||||
allItems = [...allItems, ...response.data.items];
|
||||
currentPage += 1;
|
||||
@@ -155,6 +172,9 @@
|
||||
country_key: searchCountry || undefined
|
||||
}
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data?.items) {
|
||||
allItems = response.data.items;
|
||||
currentPage = response.data.page || 1;
|
||||
@@ -169,8 +189,53 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
dialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: MultiCurrencyType) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: MultiCurrencyType) {
|
||||
editingItem = row;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar el tipo de moneda "${selectedItem.currency_type_code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteMultiCurrencyType(selectedItem.id, companyStore.activeCompany.id);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar el tipo de moneda';
|
||||
console.error('Error deleting multi-currency type:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -179,7 +244,18 @@
|
||||
<h1 class="text-2xl font-bold tracking-tight">Tipos de Moneda Múltiple</h1>
|
||||
<p class="text-muted-foreground">Gestión del catálogo de tipos de moneda múltiple</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (dialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nuevo Tipo</Button></div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button
|
||||
class="h-9"
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
dialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Tipo
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
@@ -190,10 +266,59 @@
|
||||
|
||||
<Card.Root class="border bg-background flex flex-col">
|
||||
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Monedas</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Código" bind:value={searchCode} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /><Input placeholder="País" bind:value={searchCountry} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /></div></div></Card.Header>
|
||||
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={columns} {loading} {hasMore} {loadMore} /></div></Card.Content>
|
||||
<Card.Content class="p-0">
|
||||
<div class="rounded-md border bg-background overflow-hidden">
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
{columns}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
|
||||
|
||||
<CreateDialog bind:open={dialogOpen} onSuccess={handleSuccess} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="multi-currency-types-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateDialog
|
||||
bind:open={dialogOpen}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,24 +8,28 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaPuertos } from '$lib/config/shortcuts/dashboard/general_catalogs/ports/list';
|
||||
import { portsApi } from '$lib/api/dashboard/a76/general_catalogs/ports';
|
||||
import { portsApi, type Port } from '$lib/api/dashboard/a76/general_catalogs/ports';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let editingItem = $state<Port | null>(null);
|
||||
let error = $state<string | null>(data.items?.error || null);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Puertos',
|
||||
obtenerAtajosListaPuertos({
|
||||
manejarNuevo: () => (createDialogOpen = true),
|
||||
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
},
|
||||
manejarActualizar: handleSuccess
|
||||
})
|
||||
);
|
||||
|
||||
@@ -34,12 +38,18 @@
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
let allItems = $state(data.items?.items || data.items || []);
|
||||
let allItems = $state<Port[]>(data.items?.items || data.items || []);
|
||||
let currentPage = $state(data.items?.page || 1);
|
||||
let pageSize = $state(data.items?.pageSize || data.items?.page_size || 50);
|
||||
let totalItems = $state(data.items?.total || 0);
|
||||
let loading = $state(false);
|
||||
let hasMore = $derived(allItems.length < totalItems);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1
|
||||
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
|
||||
: null
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
if (data.items?.items) {
|
||||
@@ -142,8 +152,50 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
createDialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: Port) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: Port) {
|
||||
editingItem = row;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar el puerto "${selectedItem.port_code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
await portsApi.delete(selectedItem.id, companyStore.activeCompany.id);
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar el puerto';
|
||||
console.error('Error deleting port:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -153,11 +205,13 @@
|
||||
<p class="text-muted-foreground">Catálogo de puertos</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2 h-4 w-4" />
|
||||
Actualizar
|
||||
</Button>
|
||||
<Button class="h-9" onclick={() => (createDialogOpen = true)}>
|
||||
<Button
|
||||
class="h-9"
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Puerto
|
||||
</Button>
|
||||
@@ -186,10 +240,14 @@
|
||||
<div class="rounded-md border bg-background overflow-hidden">
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
columns={createColumns(handleSuccess)}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
@@ -197,5 +255,41 @@
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="ports-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
mode={editingItem ? 'edit' : 'create'}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -5,25 +5,33 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/general_catalogs/prevalidators/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/prevalidators/create-edit-dialog.svelte';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaPrevalidadores } from '$lib/config/shortcuts/dashboard/general_catalogs/prevalidators/list';
|
||||
import { getPrevalidators } from '$lib/api/dashboard/a76/general_catalogs/prevalidators';
|
||||
import { getPrevalidators, deletePrevalidator, type Prevalidator } from '$lib/api/dashboard/a76/general_catalogs/prevalidators';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
let editingItem = $state<Prevalidator | null>(null);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
|
||||
);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Prevalidadores',
|
||||
obtenerAtajosListaPrevalidadores({
|
||||
manejarNuevo: () => (dialogOpen = true),
|
||||
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
dialogOpen = true;
|
||||
},
|
||||
manejarActualizar: handleSuccess
|
||||
})
|
||||
);
|
||||
|
||||
@@ -135,8 +143,50 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
dialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: Prevalidator) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: Prevalidator) {
|
||||
editingItem = row;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar el prevalidador "${selectedItem.code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
await deletePrevalidator(selectedItem.id, companyStore.activeCompany.id);
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar el prevalidador';
|
||||
console.error('Error deleting prevalidator:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -146,8 +196,16 @@
|
||||
<p class="text-muted-foreground">Catálogo de prevalidadores</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button>
|
||||
<Button class="h-9" onclick={() => (dialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nuevo Prevalidador</Button>
|
||||
<Button
|
||||
class="h-9"
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
dialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Prevalidador
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -167,10 +225,59 @@
|
||||
</div>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createColumns(handleSuccess)} {loading} {hasMore} {loadMore} /></div></Card.Content>
|
||||
<Card.Content class="p-0">
|
||||
<div class="rounded-md border bg-background overflow-hidden">
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
|
||||
|
||||
<CreateEditDialog bind:open={dialogOpen} onSuccess={handleSuccess} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="prevalidators-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import DataTable from '$lib/components/dashboard/seal/data-table.svelte';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte';
|
||||
import CreateEditDialog from '$lib/components/dashboard/seal/create-edit-dialog.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/seal/columns';
|
||||
import type { Seal } from '$lib/api/dashboard/a76/general_catalogs/seal';
|
||||
import { getSeals } from '$lib/api/dashboard/a76/general_catalogs/seal';
|
||||
import { getSeals, deleteSeal, type Seal } from '$lib/api/dashboard/a76/general_catalogs/seal';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaSellos } from '$lib/config/shortcuts/dashboard/general_catalogs/seal/list';
|
||||
@@ -21,13 +20,21 @@
|
||||
let totalItems = $state(0);
|
||||
|
||||
let dialogOpen = $state(false);
|
||||
let editingItem = $state<Seal | null>(null);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
|
||||
);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Sellos',
|
||||
obtenerAtajosListaSellos({
|
||||
manejarNuevo: handleCreate,
|
||||
manejarActualizar: loadInitialData
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
dialogOpen = true;
|
||||
},
|
||||
manejarActualizar: handleSuccess
|
||||
})
|
||||
);
|
||||
|
||||
@@ -125,19 +132,55 @@
|
||||
loadInitialData();
|
||||
}
|
||||
|
||||
function handleCreate() {
|
||||
function handleSuccess() {
|
||||
dialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
loadInitialData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: Seal) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: Seal) {
|
||||
editingItem = row;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
loadInitialData();
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar el sello "${selectedItem.seal}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
await deleteSeal(selectedItem.id, companyStore.activeCompany.id);
|
||||
|
||||
selectedIds = [];
|
||||
await loadInitialData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar el sello';
|
||||
console.error('Error deleting seal:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function reloadData() {
|
||||
loadInitialData();
|
||||
}
|
||||
|
||||
const columns = createColumns(handleSuccess);
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -147,11 +190,13 @@
|
||||
<p class="text-muted-foreground">Gestiona los sellos de tu empresa</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button variant="outline" size="sm" class="h-9" onclick={reloadData}>
|
||||
<RefreshCw class="mr-2 h-4 w-4" />
|
||||
Actualizar
|
||||
</Button>
|
||||
<Button class="h-9" onclick={handleCreate}>
|
||||
<Button
|
||||
class="h-9"
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
dialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Sello
|
||||
</Button>
|
||||
@@ -181,7 +226,17 @@
|
||||
</Card.Header>
|
||||
<Card.Content class="min-h-0 flex-1 p-0">
|
||||
<div class="flex h-full min-h-0 rounded-md border bg-background overflow-hidden flex-col">
|
||||
<DataTable data={allItems} {columns} {loading} {hasMore} {loadMore} />
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
@@ -190,5 +245,40 @@
|
||||
Mostrando {allItems.length} de {totalItems || allItems.length} registros
|
||||
</div>
|
||||
|
||||
<CreateEditDialog bind:open={dialogOpen} onSuccess={handleSuccess} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="seal-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -5,25 +5,33 @@
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import InfiniteDataTable from '$lib/components/dashboard/common/infinite-data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/general_catalogs/signatures/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/signatures/create-edit-dialog.svelte';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaFirmas } from '$lib/config/shortcuts/dashboard/general_catalogs/signatures/list';
|
||||
import { getSignatures } from '$lib/api/dashboard/a76/general_catalogs/signatures';
|
||||
import { getSignatures, deleteSignature, type Signature } from '$lib/api/dashboard/a76/general_catalogs/signatures';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
let editingItem = $state<Signature | null>(null);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1 ? allItems.find((item) => item.id === selectedIds[0]) ?? null : null
|
||||
);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Firmas',
|
||||
obtenerAtajosListaFirmas({
|
||||
manejarNuevo: () => (dialogOpen = true),
|
||||
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
dialogOpen = true;
|
||||
},
|
||||
manejarActualizar: handleSuccess
|
||||
})
|
||||
);
|
||||
|
||||
@@ -137,8 +145,50 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
dialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: Signature) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: Signature) {
|
||||
editingItem = row;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar la firma "${selectedItem.code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
await deleteSignature(selectedItem.id, companyStore.activeCompany.id);
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar la firma';
|
||||
console.error('Error deleting signature:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -147,7 +197,18 @@
|
||||
<h1 class="text-2xl font-bold tracking-tight">Firmas Electrónicas</h1>
|
||||
<p class="text-muted-foreground">Gestión del catálogo de firmas electrónicas</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (dialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nueva Firma</Button></div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button
|
||||
class="h-9"
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
dialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Firma
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
@@ -158,10 +219,59 @@
|
||||
|
||||
<Card.Root class="border bg-background flex flex-col">
|
||||
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Firmas</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Código" bind:value={searchCode} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /></div></div></Card.Header>
|
||||
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createColumns(handleSuccess)} {loading} {hasMore} {loadMore} /></div></Card.Content>
|
||||
<Card.Content class="p-0">
|
||||
<div class="rounded-md border bg-background overflow-hidden">
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
|
||||
|
||||
<CreateEditDialog bind:open={dialogOpen} onSuccess={handleSuccess} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="signatures-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,23 +8,27 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaConversiones } from '$lib/config/shortcuts/dashboard/general_catalogs/unit_conversions/list';
|
||||
import { getUnitConversions } from '$lib/api/dashboard/a76/general_catalogs/unit-conversions';
|
||||
import { getUnitConversions, deleteUnitConversion, type UnitConversion } from '$lib/api/dashboard/a76/general_catalogs/unit-conversions';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let editingItem = $state<UnitConversion | null>(null);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Conversiones',
|
||||
obtenerAtajosListaConversiones({
|
||||
manejarNuevo: () => (createDialogOpen = true),
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
},
|
||||
manejarRefrescar: handleSuccess
|
||||
})
|
||||
);
|
||||
@@ -34,12 +38,18 @@
|
||||
let searchTo = $state($page.url.searchParams.get('to_unit_code') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
let allItems = $state(data.conversions?.items || []);
|
||||
let allItems = $state<UnitConversion[]>(data.conversions?.items || []);
|
||||
let currentPage = $state(data.conversions?.page || 1);
|
||||
let pageSize = $state(data.conversions?.page_size || 50);
|
||||
let totalItems = $state(data.conversions?.total || 0);
|
||||
let loading = $state(false);
|
||||
let hasMore = $derived(allItems.length < totalItems);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1
|
||||
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
|
||||
: null
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
if (data.conversions) {
|
||||
@@ -148,8 +158,50 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
createDialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: UnitConversion) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: UnitConversion) {
|
||||
editingItem = row;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar esta conversión?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
await deleteUnitConversion(selectedItem.id, companyStore.activeCompany.id);
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar la conversión';
|
||||
console.error('Error deleting unit conversion:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -158,7 +210,18 @@
|
||||
<h1 class="text-2xl font-bold tracking-tight">Conversiones de Unidades</h1>
|
||||
<p class="text-muted-foreground">Catálogo de conversiones de unidades de medida</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (createDialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nueva Conversión</Button></div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button
|
||||
class="h-9"
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Conversión
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
@@ -169,10 +232,60 @@
|
||||
|
||||
<Card.Root class="border bg-background flex flex-col">
|
||||
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Conversiones</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Desde" bind:value={searchFrom} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /><Input placeholder="Hacia" bind:value={searchTo} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /></div></div></Card.Header>
|
||||
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createColumns(handleSuccess)} {loading} {hasMore} {loadMore} /></div></Card.Content>
|
||||
<Card.Content class="p-0">
|
||||
<div class="rounded-md border bg-background overflow-hidden">
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="unit-conversions-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
mode={editingItem ? 'edit' : 'create'}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,24 +8,28 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaUnidadesACE } from '$lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/ace/list';
|
||||
import { getUnitsOfMeasureACE } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { getUnitsOfMeasureACE, deleteUnitOfMeasureACE, type UnitOfMeasureACE } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let editingItem = $state<UnitOfMeasureACE | null>(null);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Unidades ACE',
|
||||
obtenerAtajosListaUnidadesACE({
|
||||
manejarNuevo: () => (createDialogOpen = true),
|
||||
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
},
|
||||
manejarActualizar: handleSuccess
|
||||
})
|
||||
);
|
||||
|
||||
@@ -34,12 +38,18 @@
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
let allItems = $state(data.ace_units?.items || []);
|
||||
let allItems = $state<UnitOfMeasureACE[]>(data.ace_units?.items || []);
|
||||
let currentPage = $state(data.ace_units?.page || 1);
|
||||
let pageSize = $state(data.ace_units?.page_size || 50);
|
||||
let totalItems = $state(data.ace_units?.total || 0);
|
||||
let loading = $state(false);
|
||||
let hasMore = $derived(allItems.length < totalItems);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1
|
||||
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
|
||||
: null
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
if (data.ace_units) {
|
||||
@@ -68,6 +78,9 @@
|
||||
companyStore.activeCompany.id,
|
||||
filters
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data) {
|
||||
allItems = response.data.items;
|
||||
currentPage = 1;
|
||||
@@ -106,6 +119,9 @@
|
||||
companyStore.activeCompany.id,
|
||||
filters
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data?.items) {
|
||||
allItems = [...allItems, ...response.data.items];
|
||||
currentPage += 1;
|
||||
@@ -134,6 +150,9 @@
|
||||
companyStore.activeCompany.id,
|
||||
filters
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data) {
|
||||
allItems = response.data.items;
|
||||
currentPage = 1;
|
||||
@@ -148,8 +167,53 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
createDialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: UnitOfMeasureACE) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: UnitOfMeasureACE) {
|
||||
editingItem = row;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar la unidad "${selectedItem.code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteUnitOfMeasureACE(selectedItem.id, companyStore.activeCompany.id);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar la unidad';
|
||||
console.error('Error deleting ACE unit:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -158,7 +222,18 @@
|
||||
<h1 class="text-2xl font-bold tracking-tight">Unidades de Medida ACE</h1>
|
||||
<p class="text-muted-foreground">Catálogo de unidades de medida ACE</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (createDialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nueva Unidad</Button></div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button
|
||||
class="h-9"
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Unidad
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
@@ -169,10 +244,59 @@
|
||||
|
||||
<Card.Root class="border bg-background flex flex-col">
|
||||
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Unidades ACE</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Clave" bind:value={searchCode} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /><Input placeholder="Descripción" bind:value={searchDesc} oninput={handleSearch} class="h-9 w-44 bg-card lg:w-64" /></div></div></Card.Header>
|
||||
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable data={allItems} columns={createColumns(handleSuccess)} {loading} {hasMore} {loadMore} /></div></Card.Content>
|
||||
<Card.Content class="p-0">
|
||||
<div class="rounded-md border bg-background overflow-hidden">
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="ace-units-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,24 +8,28 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaUnidadesAmericanas } from '$lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/american/list';
|
||||
import { getUnitsOfMeasureAmerican } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { getUnitsOfMeasureAmerican, deleteUnitOfMeasureAmerican, type UnitOfMeasureAmerican } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let editingItem = $state<UnitOfMeasureAmerican | null>(null);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Unidades Americanas',
|
||||
obtenerAtajosListaUnidadesAmericanas({
|
||||
manejarNuevo: () => (createDialogOpen = true),
|
||||
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
},
|
||||
manejarActualizar: handleSuccess
|
||||
})
|
||||
);
|
||||
|
||||
@@ -34,12 +38,18 @@
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
let allItems = $state(data.american_units?.items || []);
|
||||
let allItems = $state<UnitOfMeasureAmerican[]>(data.american_units?.items || []);
|
||||
let currentPage = $state(data.american_units?.page || 1);
|
||||
let pageSize = $state(data.american_units?.page_size || 50);
|
||||
let totalItems = $state(data.american_units?.total || 0);
|
||||
let loading = $state(false);
|
||||
let hasMore = $derived(allItems.length < totalItems);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1
|
||||
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
|
||||
: null
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
if (data.american_units) {
|
||||
@@ -68,6 +78,9 @@
|
||||
companyStore.activeCompany.id,
|
||||
filters
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data) {
|
||||
allItems = response.data.items;
|
||||
currentPage = 1;
|
||||
@@ -106,6 +119,9 @@
|
||||
companyStore.activeCompany.id,
|
||||
filters
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data?.items) {
|
||||
allItems = [...allItems, ...response.data.items];
|
||||
currentPage += 1;
|
||||
@@ -134,6 +150,9 @@
|
||||
companyStore.activeCompany.id,
|
||||
filters
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data) {
|
||||
allItems = response.data.items;
|
||||
currentPage = 1;
|
||||
@@ -148,8 +167,53 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
createDialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: UnitOfMeasureAmerican) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: UnitOfMeasureAmerican) {
|
||||
editingItem = row;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar la unidad "${selectedItem.code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteUnitOfMeasureAmerican(selectedItem.id, companyStore.activeCompany.id);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar la unidad';
|
||||
console.error('Error deleting American unit:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -158,7 +222,18 @@
|
||||
<h1 class="text-2xl font-bold tracking-tight">Unidades de Medida Americanas</h1>
|
||||
<p class="text-muted-foreground">Catálogo de unidades de medida Americanas</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (createDialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nueva Unidad</Button></div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button
|
||||
class="h-9"
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Unidad
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
@@ -169,10 +244,59 @@
|
||||
|
||||
<Card.Root class="border bg-background flex flex-col">
|
||||
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Unidades Americanas</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Clave" bind:value={searchCode} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /><Input placeholder="Descripción" bind:value={searchDesc} oninput={handleSearch} class="h-9 w-44 bg-card lg:w-64" /></div></div></Card.Header>
|
||||
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable columns={createColumns(handleSuccess)} data={allItems} {loading} {hasMore} {loadMore} /></div></Card.Content>
|
||||
<Card.Content class="p-0">
|
||||
<div class="rounded-md border bg-background overflow-hidden">
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="american-units-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,24 +8,28 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaUnidadesAduanas } from '$lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/customs/list';
|
||||
import { getUnitsOfMeasureCustoms } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { getUnitsOfMeasureCustoms, deleteUnitOfMeasureCustoms, type UnitOfMeasureCustoms } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let editingItem = $state<UnitOfMeasureCustoms | null>(null);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Unidades Aduanas',
|
||||
obtenerAtajosListaUnidadesAduanas({
|
||||
manejarNuevo: () => (createDialogOpen = true),
|
||||
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
},
|
||||
manejarActualizar: handleSuccess
|
||||
})
|
||||
);
|
||||
|
||||
@@ -34,12 +38,18 @@
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
let allItems = $state(data.customs_units?.items || []);
|
||||
let allItems = $state<UnitOfMeasureCustoms[]>(data.customs_units?.items || []);
|
||||
let currentPage = $state(data.customs_units?.page || 1);
|
||||
let pageSize = $state(data.customs_units?.page_size || 50);
|
||||
let totalItems = $state(data.customs_units?.total || 0);
|
||||
let loading = $state(false);
|
||||
let hasMore = $derived(allItems.length < totalItems);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1
|
||||
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
|
||||
: null
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
if (data.customs_units) {
|
||||
@@ -68,6 +78,9 @@
|
||||
companyStore.activeCompany.id,
|
||||
filters
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data) {
|
||||
allItems = response.data.items;
|
||||
currentPage = 1;
|
||||
@@ -106,6 +119,9 @@
|
||||
companyStore.activeCompany.id,
|
||||
filters
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data?.items) {
|
||||
allItems = [...allItems, ...response.data.items];
|
||||
currentPage += 1;
|
||||
@@ -134,6 +150,9 @@
|
||||
companyStore.activeCompany.id,
|
||||
filters
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data) {
|
||||
allItems = response.data.items;
|
||||
currentPage = 1;
|
||||
@@ -148,8 +167,53 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
createDialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: UnitOfMeasureCustoms) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: UnitOfMeasureCustoms) {
|
||||
editingItem = row;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar la unidad "${selectedItem.code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteUnitOfMeasureCustoms(selectedItem.id, companyStore.activeCompany.id);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar la unidad';
|
||||
console.error('Error deleting customs unit:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -158,7 +222,18 @@
|
||||
<h1 class="text-2xl font-bold tracking-tight">Unidades de Medida Aduanas MEX</h1>
|
||||
<p class="text-muted-foreground">Catálogo de unidades de medida para aduanas mexicanas</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (createDialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nueva Unidad</Button></div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button
|
||||
class="h-9"
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Unidad
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
@@ -169,10 +244,59 @@
|
||||
|
||||
<Card.Root class="border bg-background flex flex-col">
|
||||
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Unidades Aduanas MX</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Clave" bind:value={searchCode} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /><Input placeholder="Descripción" bind:value={searchDesc} oninput={handleSearch} class="h-9 w-44 bg-card lg:w-64" /></div></div></Card.Header>
|
||||
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable columns={createColumns(handleSuccess)} data={allItems} {loading} {hasMore} {loadMore} /></div></Card.Content>
|
||||
<Card.Content class="p-0">
|
||||
<div class="rounded-md border bg-background overflow-hidden">
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="customs-units-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,24 +8,28 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaUnidadesGeneral } from '$lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/general/list';
|
||||
import { getUnitsOfMeasureGeneral } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { getUnitsOfMeasureGeneral, deleteUnitOfMeasureGeneral, type UnitOfMeasureGeneral } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let editingItem = $state<UnitOfMeasureGeneral | null>(null);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Unidades de Medida',
|
||||
obtenerAtajosListaUnidadesGeneral({
|
||||
manejarNuevo: () => (createDialogOpen = true),
|
||||
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
},
|
||||
manejarActualizar: handleSuccess
|
||||
})
|
||||
);
|
||||
|
||||
@@ -34,12 +38,18 @@
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
let allItems = $state(data.general_units?.items || []);
|
||||
let allItems = $state<UnitOfMeasureGeneral[]>(data.general_units?.items || []);
|
||||
let currentPage = $state(data.general_units?.page || 1);
|
||||
let pageSize = $state(data.general_units?.page_size || 50);
|
||||
let totalItems = $state(data.general_units?.total || 0);
|
||||
let loading = $state(false);
|
||||
let hasMore = $derived(allItems.length < totalItems);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1
|
||||
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
|
||||
: null
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
if (data.general_units) {
|
||||
@@ -68,6 +78,9 @@
|
||||
companyStore.activeCompany.id,
|
||||
filters
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data) {
|
||||
allItems = response.data.items;
|
||||
currentPage = 1;
|
||||
@@ -106,6 +119,9 @@
|
||||
companyStore.activeCompany.id,
|
||||
filters
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data?.items) {
|
||||
allItems = [...allItems, ...response.data.items];
|
||||
currentPage += 1;
|
||||
@@ -134,6 +150,9 @@
|
||||
companyStore.activeCompany.id,
|
||||
filters
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data) {
|
||||
allItems = response.data.items;
|
||||
currentPage = 1;
|
||||
@@ -148,8 +167,53 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
createDialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: UnitOfMeasureGeneral) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: UnitOfMeasureGeneral) {
|
||||
editingItem = row;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar la unidad "${selectedItem.code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteUnitOfMeasureGeneral(selectedItem.id, companyStore.activeCompany.id);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar la unidad';
|
||||
console.error('Error deleting general unit:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -158,7 +222,18 @@
|
||||
<h1 class="text-2xl font-bold tracking-tight">Unidades de Medida</h1>
|
||||
<p class="text-muted-foreground">Catálogo general de unidades de medida</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (createDialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nueva Unidad</Button></div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button
|
||||
class="h-9"
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Unidad
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
@@ -169,10 +244,59 @@
|
||||
|
||||
<Card.Root class="border bg-background flex flex-col">
|
||||
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Unidades</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Clave" bind:value={searchCode} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /><Input placeholder="Descripción" bind:value={searchDesc} oninput={handleSearch} class="h-9 w-44 bg-card lg:w-64" /></div></div></Card.Header>
|
||||
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable columns={createColumns(handleSuccess)} data={allItems} {loading} {hasMore} {loadMore} /></div></Card.Content>
|
||||
<Card.Content class="p-0">
|
||||
<div class="rounded-md border bg-background overflow-hidden">
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="general-units-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,24 +8,28 @@
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { Pencil, Plus, Trash2 } from 'lucide-svelte';
|
||||
import { useShortcuts } from '$lib/hooks/use-shortcuts';
|
||||
import { obtenerAtajosListaUnidadesOMA } from '$lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/oma/list';
|
||||
import { getUnitsOfMeasureOMA } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { getUnitsOfMeasureOMA, deleteUnitOfMeasureOMA, type UnitOfMeasureOMA } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let editingItem = $state<UnitOfMeasureOMA | null>(null);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
|
||||
// Atajos
|
||||
useShortcuts(
|
||||
'Lista Unidades OMA',
|
||||
obtenerAtajosListaUnidadesOMA({
|
||||
manejarNuevo: () => (createDialogOpen = true),
|
||||
manejarActualizar: () => goto($page.url, { invalidateAll: true }),
|
||||
manejarNuevo: () => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
},
|
||||
manejarActualizar: handleSuccess
|
||||
})
|
||||
);
|
||||
|
||||
@@ -34,12 +38,18 @@
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
let allItems = $state(data.oma_units?.items || []);
|
||||
let allItems = $state<UnitOfMeasureOMA[]>(data.oma_units?.items || []);
|
||||
let currentPage = $state(data.oma_units?.page || 1);
|
||||
let pageSize = $state(data.oma_units?.page_size || 50);
|
||||
let totalItems = $state(data.oma_units?.total || 0);
|
||||
let loading = $state(false);
|
||||
let hasMore = $derived(allItems.length < totalItems);
|
||||
let selectedIds = $state<(string | number)[]>([]);
|
||||
const selectedItem = $derived(
|
||||
selectedIds.length === 1
|
||||
? allItems.find((item) => String(item.id) === String(selectedIds[0])) ?? null
|
||||
: null
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
if (data.oma_units) {
|
||||
@@ -68,6 +78,9 @@
|
||||
companyStore.activeCompany.id,
|
||||
filters
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data) {
|
||||
allItems = response.data.items;
|
||||
currentPage = 1;
|
||||
@@ -106,6 +119,9 @@
|
||||
companyStore.activeCompany.id,
|
||||
filters
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data?.items) {
|
||||
allItems = [...allItems, ...response.data.items];
|
||||
currentPage += 1;
|
||||
@@ -134,6 +150,9 @@
|
||||
companyStore.activeCompany.id,
|
||||
filters
|
||||
);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
if (response.data) {
|
||||
allItems = response.data.items;
|
||||
currentPage = 1;
|
||||
@@ -148,8 +167,53 @@
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
createDialogOpen = false;
|
||||
editingItem = null;
|
||||
selectedIds = [];
|
||||
reloadData();
|
||||
}
|
||||
|
||||
function handleRowClick(row: UnitOfMeasureOMA) {
|
||||
const id = row.id;
|
||||
selectedIds = selectedIds.includes(id) ? [] : [id];
|
||||
}
|
||||
|
||||
function handleRowDoubleClick(row: UnitOfMeasureOMA) {
|
||||
editingItem = row;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
function handleEditSelected() {
|
||||
if (!selectedItem) return;
|
||||
editingItem = selectedItem;
|
||||
createDialogOpen = true;
|
||||
}
|
||||
|
||||
async function handleDeleteSelected() {
|
||||
if (!selectedItem || !companyStore.activeCompany) return;
|
||||
|
||||
if (!confirm(`¿Estás seguro de eliminar la unidad "${selectedItem.code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteUnitOfMeasureOMA(selectedItem.id, companyStore.activeCompany.id);
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
selectedIds = [];
|
||||
await reloadData();
|
||||
} catch (err) {
|
||||
error = 'Error al eliminar la unidad';
|
||||
console.error('Error deleting OMA unit:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@@ -158,7 +222,18 @@
|
||||
<h1 class="text-2xl font-bold tracking-tight">Unidades de Medida OMA</h1>
|
||||
<p class="text-muted-foreground">Catálogo de unidades de medida OMA</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-3"><Button variant="outline" size="sm" class="h-9" onclick={reloadData}><RefreshCw class="mr-2 h-4 w-4" />Actualizar</Button><Button class="h-9" onclick={() => (createDialogOpen = true)}><Plus class="mr-2 h-4 w-4" />Nueva Unidad</Button></div>
|
||||
<div class="flex items-center gap-3">
|
||||
<Button
|
||||
class="h-9"
|
||||
onclick={() => {
|
||||
editingItem = null;
|
||||
createDialogOpen = true;
|
||||
}}
|
||||
>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Unidad
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if error}
|
||||
@@ -169,10 +244,59 @@
|
||||
|
||||
<Card.Root class="border bg-background flex flex-col">
|
||||
<Card.Header><div class="flex flex-wrap items-center justify-between gap-3"><Card.Title>Listado de Unidades OMA</Card.Title><div class="flex flex-wrap items-center gap-2"><Input placeholder="Clave" bind:value={searchCode} oninput={handleSearch} class="h-9 w-36 bg-card lg:w-44" /><Input placeholder="Descripción" bind:value={searchDesc} oninput={handleSearch} class="h-9 w-44 bg-card lg:w-64" /></div></div></Card.Header>
|
||||
<Card.Content class="p-0"><div class="rounded-md border bg-background overflow-hidden"><InfiniteDataTable columns={createColumns(handleSuccess)} data={allItems} {loading} {hasMore} {loadMore} /></div></Card.Content>
|
||||
<Card.Content class="p-0">
|
||||
<div class="rounded-md border bg-background overflow-hidden">
|
||||
<InfiniteDataTable
|
||||
data={allItems}
|
||||
columns={createColumns()}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
{selectedIds}
|
||||
onSelectedIdsChange={(ids) => (selectedIds = ids)}
|
||||
onRowClick={handleRowClick}
|
||||
onRowDoubleClick={handleRowDoubleClick}
|
||||
/>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<div class="flex-none text-sm text-muted-foreground">Mostrando {allItems.length} de {totalItems} registros</div>
|
||||
|
||||
<CreateEditDialog bind:open={createDialogOpen} onSuccess={handleSuccess} />
|
||||
<div class="h-20"></div>
|
||||
|
||||
<div
|
||||
id="oma-units-list-footer"
|
||||
class="fixed right-0 bottom-0 left-0 z-50 ml-[calc(var(--sidebar-width))] border-t bg-background/95 shadow-lg backdrop-blur group-has-data-[state=collapsed]/sidebar-wrapper:ml-[calc(var(--sidebar-width-icon))] supports-[backdrop-filter]:bg-background/80"
|
||||
>
|
||||
<div class="mx-auto max-w-[1400px] px-4 py-4">
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleEditSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
>
|
||||
<Pencil size={16} class="mr-2" />
|
||||
Editar
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={handleDeleteSelected}
|
||||
disabled={selectedIds.length !== 1}
|
||||
class="text-destructive hover:bg-destructive/10 hover:text-destructive"
|
||||
>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Eliminar
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user