feat: Implement general catalogs for INPC, Legends, Multi-Currency Types, Packages, Ports, Prevalidators, Signatures, Unit Conversions, and Units of Measure
- Added INPC management page with search functionality. - Created Legends management page with filters for code and description. - Implemented Multi-Currency Types management with search capabilities. - Developed Packages management page with filtering options. - Introduced Ports management page with authentication and data fetching. - Added Prevalidators management page with search filters. - Implemented Signatures management page with search by name and position. - Created Unit Conversions management page for conversion factors. - Developed Units of Measure management pages for ACE, American, and OMA with data tables and create/edit dialogs.
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface ClassificationConcept {
|
||||
id: number;
|
||||
classification: string;
|
||||
description?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface ClassificationConceptCreate {
|
||||
classification: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface ClassificationConceptUpdate extends Partial<ClassificationConceptCreate> {}
|
||||
|
||||
export interface ClassificationConceptListResponse {
|
||||
items: ClassificationConcept[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getClassificationConcepts(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<ClassificationConceptListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/classification_concepts?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getClassificationConcept(id: number): Promise<ClassificationConcept> {
|
||||
const response = await api.get(`/a76/classification_concepts/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createClassificationConcept(data: ClassificationConceptCreate): Promise<ClassificationConcept> {
|
||||
const response = await api.post('/a76/classification_concepts', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateClassificationConcept(id: number, data: ClassificationConceptUpdate): Promise<ClassificationConcept> {
|
||||
const response = await api.patch(`/a76/classification_concepts/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteClassificationConcept(id: number): Promise<void> {
|
||||
await api.delete(`/a76/classification_concepts/${id}`);
|
||||
}
|
||||
91
frontend/src/lib/api/dashboard/a76/company.ts
Normal file
91
frontend/src/lib/api/dashboard/a76/company.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Company {
|
||||
id: number;
|
||||
tenant_id: number;
|
||||
name: string | null;
|
||||
rfc: string | null;
|
||||
main_activity: string | null;
|
||||
program: string | null;
|
||||
program_number: string | null;
|
||||
prosec: number | null;
|
||||
prosec_authorization: string | null;
|
||||
manufacturer_id: string | null;
|
||||
broker_company: string | null;
|
||||
responsible: string | null;
|
||||
responsible_name: string | null;
|
||||
responsible_last_name: string | null;
|
||||
responsible_mother_last_name: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface CompanyCreate {
|
||||
name?: string | null;
|
||||
rfc?: string | null;
|
||||
main_activity?: string | null;
|
||||
program?: string | null;
|
||||
program_number?: string | null;
|
||||
prosec?: number | null;
|
||||
prosec_authorization?: string | null;
|
||||
manufacturer_id?: string | null;
|
||||
broker_company?: string | null;
|
||||
responsible?: string | null;
|
||||
responsible_name?: string | null;
|
||||
responsible_last_name?: string | null;
|
||||
responsible_mother_last_name?: string | null;
|
||||
}
|
||||
|
||||
export interface CompanyUpdate {
|
||||
name?: string | null;
|
||||
rfc?: string | null;
|
||||
main_activity?: string | null;
|
||||
program?: string | null;
|
||||
program_number?: string | null;
|
||||
prosec?: number | null;
|
||||
prosec_authorization?: string | null;
|
||||
manufacturer_id?: string | null;
|
||||
broker_company?: string | null;
|
||||
responsible?: string | null;
|
||||
responsible_name?: string | null;
|
||||
responsible_last_name?: string | null;
|
||||
responsible_mother_last_name?: string | null;
|
||||
}
|
||||
|
||||
export interface CompanyListResponse {
|
||||
items: Company[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getCompanies(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<CompanyListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/company?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
export async function getCompany(id: number): Promise<ApiResponse<Company>> {
|
||||
return await api.get(`/a76/company/${id}`);
|
||||
}
|
||||
|
||||
export async function createCompany(data: CompanyCreate): Promise<ApiResponse<Company>> {
|
||||
return await api.post(`/a76/company`, data);
|
||||
}
|
||||
|
||||
export async function updateCompany(id: number, data: CompanyUpdate): Promise<ApiResponse<Company>> {
|
||||
return await api.put(`/a76/company/${id}`, data);
|
||||
}
|
||||
|
||||
export async function deleteCompany(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/company/${id}`);
|
||||
}
|
||||
79
frontend/src/lib/api/dashboard/a76/concepts.ts
Normal file
79
frontend/src/lib/api/dashboard/a76/concepts.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Concept {
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
description_en?: string;
|
||||
detailed_description?: string;
|
||||
priority?: number;
|
||||
priority_ame?: number;
|
||||
first_total?: boolean;
|
||||
type?: string;
|
||||
is_printed?: boolean;
|
||||
section?: number;
|
||||
classification?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface ConceptCreate {
|
||||
code: string;
|
||||
description?: string;
|
||||
description_en?: string;
|
||||
detailed_description?: string;
|
||||
priority?: number;
|
||||
priority_ame?: number;
|
||||
first_total?: boolean;
|
||||
type?: string;
|
||||
is_printed?: boolean;
|
||||
section?: number;
|
||||
classification?: string;
|
||||
}
|
||||
|
||||
export interface ConceptUpdate extends Partial<ConceptCreate> {}
|
||||
|
||||
export interface ConceptListResponse {
|
||||
items: Concept[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getConcepts(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<ConceptListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/concepts?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getConcept(id: number): Promise<Concept> {
|
||||
const response = await api.get(`/a76/concepts/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createConcept(data: ConceptCreate): Promise<Concept> {
|
||||
const response = await api.post('/a76/concepts', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateConcept(id: number, data: ConceptUpdate): Promise<Concept> {
|
||||
const response = await api.patch(`/a76/concepts/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteConcept(id: number): Promise<void> {
|
||||
await api.delete(`/a76/concepts/${id}`);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface CustomsBrokerConcept {
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
description_en?: string;
|
||||
detailed_description?: string;
|
||||
priority?: number;
|
||||
first_total?: boolean;
|
||||
type?: string;
|
||||
is_printed?: boolean;
|
||||
section?: number;
|
||||
classification?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface CustomsBrokerConceptCreate {
|
||||
code: string;
|
||||
description?: string;
|
||||
description_en?: string;
|
||||
detailed_description?: string;
|
||||
priority?: number;
|
||||
first_total?: boolean;
|
||||
type?: string;
|
||||
is_printed?: boolean;
|
||||
section?: number;
|
||||
classification?: string;
|
||||
}
|
||||
|
||||
export interface CustomsBrokerConceptUpdate extends Partial<CustomsBrokerConceptCreate> {}
|
||||
|
||||
export interface CustomsBrokerConceptListResponse {
|
||||
items: CustomsBrokerConcept[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getCustomsBrokerConcepts(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<CustomsBrokerConceptListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/customs_broker_concepts?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getCustomsBrokerConcept(id: number): Promise<CustomsBrokerConcept> {
|
||||
const response = await api.get(`/a76/customs_broker_concepts/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createCustomsBrokerConcept(data: CustomsBrokerConceptCreate): Promise<CustomsBrokerConcept> {
|
||||
const response = await api.post('/a76/customs_broker_concepts', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateCustomsBrokerConcept(id: number, data: CustomsBrokerConceptUpdate): Promise<CustomsBrokerConcept> {
|
||||
const response = await api.patch(`/a76/customs_broker_concepts/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteCustomsBrokerConcept(id: number): Promise<void> {
|
||||
await api.delete(`/a76/customs_broker_concepts/${id}`);
|
||||
}
|
||||
61
frontend/src/lib/api/dashboard/a76/doda.ts
Normal file
61
frontend/src/lib/api/dashboard/a76/doda.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface DODA {
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface DODACreate {
|
||||
code: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface DODAUpdate extends Partial<DODACreate> {}
|
||||
|
||||
export interface DODAListResponse {
|
||||
items: DODA[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getDODAs(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<DODAListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/doda?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getDODA(id: number): Promise<DODA> {
|
||||
const response = await api.get(`/a76/doda/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createDODA(data: DODACreate): Promise<DODA> {
|
||||
const response = await api.post('/a76/doda', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateDODA(id: number, data: DODAUpdate): Promise<DODA> {
|
||||
const response = await api.patch(`/a76/doda/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteDODA(id: number): Promise<void> {
|
||||
await api.delete(`/a76/doda/${id}`);
|
||||
}
|
||||
61
frontend/src/lib/api/dashboard/a76/electronic-notices.ts
Normal file
61
frontend/src/lib/api/dashboard/a76/electronic-notices.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface ElectronicNotice {
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface ElectronicNoticeCreate {
|
||||
code: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface ElectronicNoticeUpdate extends Partial<ElectronicNoticeCreate> {}
|
||||
|
||||
export interface ElectronicNoticeListResponse {
|
||||
items: ElectronicNotice[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getElectronicNotices(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<ElectronicNoticeListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/electronic_notices?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getElectronicNotice(id: number): Promise<ElectronicNotice> {
|
||||
const response = await api.get(`/a76/electronic_notices/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createElectronicNotice(data: ElectronicNoticeCreate): Promise<ElectronicNotice> {
|
||||
const response = await api.post('/a76/electronic_notices', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateElectronicNotice(id: number, data: ElectronicNoticeUpdate): Promise<ElectronicNotice> {
|
||||
const response = await api.patch(`/a76/electronic_notices/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteElectronicNotice(id: number): Promise<void> {
|
||||
await api.delete(`/a76/electronic_notices/${id}`);
|
||||
}
|
||||
63
frontend/src/lib/api/dashboard/a76/equivalencies.ts
Normal file
63
frontend/src/lib/api/dashboard/a76/equivalencies.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Equivalency {
|
||||
id: number;
|
||||
fraccion_mex: string;
|
||||
fraccion_us: string;
|
||||
description?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface EquivalencyCreate {
|
||||
fraccion_mex: string;
|
||||
fraccion_us: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface EquivalencyUpdate extends Partial<EquivalencyCreate> {}
|
||||
|
||||
export interface EquivalencyListResponse {
|
||||
items: Equivalency[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getEquivalencies(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<EquivalencyListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/equivalencies?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getEquivalency(id: number): Promise<Equivalency> {
|
||||
const response = await api.get(`/a76/equivalencies/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createEquivalency(data: EquivalencyCreate): Promise<Equivalency> {
|
||||
const response = await api.post('/a76/equivalencies', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateEquivalency(id: number, data: EquivalencyUpdate): Promise<Equivalency> {
|
||||
const response = await api.patch(`/a76/equivalencies/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteEquivalency(id: number): Promise<void> {
|
||||
await api.delete(`/a76/equivalencies/${id}`);
|
||||
}
|
||||
61
frontend/src/lib/api/dashboard/a76/error-catalogs.ts
Normal file
61
frontend/src/lib/api/dashboard/a76/error-catalogs.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface ErrorCatalog {
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface ErrorCatalogCreate {
|
||||
code: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface ErrorCatalogUpdate extends Partial<ErrorCatalogCreate> {}
|
||||
|
||||
export interface ErrorCatalogListResponse {
|
||||
items: ErrorCatalog[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getErrorCatalogs(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<ErrorCatalogListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/error_catalogs?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getErrorCatalog(id: number): Promise<ErrorCatalog> {
|
||||
const response = await api.get(`/a76/error_catalogs/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createErrorCatalog(data: ErrorCatalogCreate): Promise<ErrorCatalog> {
|
||||
const response = await api.post('/a76/error_catalogs', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateErrorCatalog(id: number, data: ErrorCatalogUpdate): Promise<ErrorCatalog> {
|
||||
const response = await api.patch(`/a76/error_catalogs/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteErrorCatalog(id: number): Promise<void> {
|
||||
await api.delete(`/a76/error_catalogs/${id}`);
|
||||
}
|
||||
62
frontend/src/lib/api/dashboard/a76/identifiers.ts
Normal file
62
frontend/src/lib/api/dashboard/a76/identifiers.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Identifier {
|
||||
id: number;
|
||||
code: string;
|
||||
description: string | null;
|
||||
level: string | null;
|
||||
complement: string | null;
|
||||
company_id: number;
|
||||
tenant_id: number;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface IdentifierCreate {
|
||||
code: string;
|
||||
description?: string | null;
|
||||
level?: string | null;
|
||||
complement?: string | null;
|
||||
company_id: number;
|
||||
}
|
||||
|
||||
export interface IdentifierUpdate {
|
||||
code?: string;
|
||||
description?: string | null;
|
||||
level?: string | null;
|
||||
complement?: string | null;
|
||||
}
|
||||
|
||||
export interface IdentifierListResponse {
|
||||
items: Identifier[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getIdentifiers(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<IdentifierListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/identifiers?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
export async function createIdentifier(data: IdentifierCreate): Promise<ApiResponse<Identifier>> {
|
||||
return await api.post('/a76/identifiers', data);
|
||||
}
|
||||
|
||||
export async function updateIdentifier(id: number, data: IdentifierUpdate): Promise<ApiResponse<Identifier>> {
|
||||
return await api.put(`/a76/identifiers/${id}`, data);
|
||||
}
|
||||
|
||||
export async function deleteIdentifier(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/identifiers/${id}`);
|
||||
}
|
||||
@@ -4,3 +4,16 @@
|
||||
export * from './classes';
|
||||
export * from './packages';
|
||||
export * from './exchange-rate';
|
||||
export * from './concepts';
|
||||
export * from './customs-broker-concepts';
|
||||
export * from './classification-concepts';
|
||||
export * from './unit-conversions';
|
||||
export * from './equivalencies';
|
||||
export * from './multi-currency-types';
|
||||
export * from './inpc';
|
||||
export * from './legends';
|
||||
export * from './signatures';
|
||||
export * from './error-catalogs';
|
||||
export * from './doda';
|
||||
export * from './prevalidators';
|
||||
export * from './electronic-notices';
|
||||
|
||||
63
frontend/src/lib/api/dashboard/a76/inpc.ts
Normal file
63
frontend/src/lib/api/dashboard/a76/inpc.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface INPC {
|
||||
id: number;
|
||||
year: string;
|
||||
month: string;
|
||||
value?: number;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface INPCCreate {
|
||||
year: string;
|
||||
month: string;
|
||||
value?: number;
|
||||
}
|
||||
|
||||
export interface INPCUpdate extends Partial<INPCCreate> {}
|
||||
|
||||
export interface INPCListResponse {
|
||||
items: INPC[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getINPCs(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<INPCListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/inpc?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getINPC(id: number): Promise<INPC> {
|
||||
const response = await api.get(`/a76/inpc/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createINPC(data: INPCCreate): Promise<INPC> {
|
||||
const response = await api.post('/a76/inpc', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateINPC(id: number, data: INPCUpdate): Promise<INPC> {
|
||||
const response = await api.patch(`/a76/inpc/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteINPC(id: number): Promise<void> {
|
||||
await api.delete(`/a76/inpc/${id}`);
|
||||
}
|
||||
61
frontend/src/lib/api/dashboard/a76/legends.ts
Normal file
61
frontend/src/lib/api/dashboard/a76/legends.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Legend {
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface LegendCreate {
|
||||
code: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface LegendUpdate extends Partial<LegendCreate> {}
|
||||
|
||||
export interface LegendListResponse {
|
||||
items: Legend[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getLegends(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<LegendListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/legends?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getLegend(id: number): Promise<Legend> {
|
||||
const response = await api.get(`/a76/legends/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createLegend(data: LegendCreate): Promise<Legend> {
|
||||
const response = await api.post('/a76/legends', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateLegend(id: number, data: LegendUpdate): Promise<Legend> {
|
||||
const response = await api.patch(`/a76/legends/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteLegend(id: number): Promise<void> {
|
||||
await api.delete(`/a76/legends/${id}`);
|
||||
}
|
||||
61
frontend/src/lib/api/dashboard/a76/multi-currency-types.ts
Normal file
61
frontend/src/lib/api/dashboard/a76/multi-currency-types.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface MultiCurrencyType {
|
||||
id: number;
|
||||
key: string;
|
||||
description?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface MultiCurrencyTypeCreate {
|
||||
key: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface MultiCurrencyTypeUpdate extends Partial<MultiCurrencyTypeCreate> {}
|
||||
|
||||
export interface MultiCurrencyTypeListResponse {
|
||||
items: MultiCurrencyType[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getMultiCurrencyTypes(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<MultiCurrencyTypeListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/multi_currency_types?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getMultiCurrencyType(id: number): Promise<MultiCurrencyType> {
|
||||
const response = await api.get(`/a76/multi_currency_types/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createMultiCurrencyType(data: MultiCurrencyTypeCreate): Promise<MultiCurrencyType> {
|
||||
const response = await api.post('/a76/multi_currency_types', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateMultiCurrencyType(id: number, data: MultiCurrencyTypeUpdate): Promise<MultiCurrencyType> {
|
||||
const response = await api.patch(`/a76/multi_currency_types/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteMultiCurrencyType(id: number): Promise<void> {
|
||||
await api.delete(`/a76/multi_currency_types/${id}`);
|
||||
}
|
||||
@@ -1,122 +1,78 @@
|
||||
/**
|
||||
* API para gestión de Packages (Bultos/Embalajes A76)
|
||||
*/
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Package {
|
||||
id: number;
|
||||
tenant_id: number;
|
||||
company_id: number;
|
||||
key: string;
|
||||
description_es: string | null;
|
||||
description_en: string | null;
|
||||
weight_unit: number | null;
|
||||
plurals: string | null;
|
||||
plural_in: string | null;
|
||||
code_ace: string | null;
|
||||
code_aamex: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
id: number;
|
||||
tenant_id: number;
|
||||
company_id: number;
|
||||
key: string;
|
||||
description_es: string | null;
|
||||
description_en: string | null;
|
||||
weight_unit: number | null;
|
||||
plurals: string | null;
|
||||
plural_in: string | null;
|
||||
code_ace: string | null;
|
||||
code_aamex: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface PackageCreate {
|
||||
key: string;
|
||||
description_es?: string | null;
|
||||
description_en?: string | null;
|
||||
weight_unit?: number | null;
|
||||
plurals?: string | null;
|
||||
plural_in?: string | null;
|
||||
code_ace?: string | null;
|
||||
code_aamex?: string | null;
|
||||
key: string;
|
||||
description_es?: string | null;
|
||||
description_en?: string | null;
|
||||
weight_unit?: number | null;
|
||||
plurals?: string | null;
|
||||
plural_in?: string | null;
|
||||
code_ace?: string | null;
|
||||
code_aamex?: string | null;
|
||||
company_id: number;
|
||||
}
|
||||
|
||||
export interface PackageUpdate {
|
||||
key?: string;
|
||||
description_es?: string | null;
|
||||
description_en?: string | null;
|
||||
weight_unit?: number | null;
|
||||
plurals?: string | null;
|
||||
plural_in?: string | null;
|
||||
code_ace?: string | null;
|
||||
code_aamex?: string | null;
|
||||
key?: string;
|
||||
description_es?: string | null;
|
||||
description_en?: string | null;
|
||||
weight_unit?: number | null;
|
||||
plurals?: string | null;
|
||||
plural_in?: string | null;
|
||||
code_ace?: string | null;
|
||||
code_aamex?: string | null;
|
||||
}
|
||||
|
||||
export interface PackageListResponse {
|
||||
items: Package[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
items: Package[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export interface PackageFilters {
|
||||
key?: string;
|
||||
description_es?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener lista de packages con paginación
|
||||
*/
|
||||
export async function getPackages(
|
||||
companyId: number,
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters?: PackageFilters
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<PackageListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
company_id: companyId.toString(),
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString()
|
||||
});
|
||||
|
||||
if (filters?.key) {
|
||||
params.append('key', filters.key);
|
||||
}
|
||||
if (filters?.description_es) {
|
||||
params.append('description_es', filters.description_es);
|
||||
}
|
||||
|
||||
return api.get<PackageListResponse>(`/v1/a76/packages/?${params.toString()}`);
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/packages?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtener un package por ID
|
||||
*/
|
||||
export async function getPackage(
|
||||
packageId: number,
|
||||
companyId: number
|
||||
): Promise<ApiResponse<Package>> {
|
||||
return api.get<Package>(`/v1/a76/packages/${packageId}?company_id=${companyId}`);
|
||||
export async function getPackage(id: number): Promise<ApiResponse<Package>> {
|
||||
return await api.get(`/a76/packages/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Crear un nuevo package
|
||||
*/
|
||||
export async function createPackage(
|
||||
data: PackageCreate,
|
||||
companyId: number
|
||||
): Promise<ApiResponse<Package>> {
|
||||
return api.post<Package>(`/v1/a76/packages/?company_id=${companyId}`, data);
|
||||
export async function createPackage(data: PackageCreate): Promise<ApiResponse<Package>> {
|
||||
return await api.post(`/a76/packages`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualizar un package existente
|
||||
*/
|
||||
export async function updatePackage(
|
||||
packageId: number,
|
||||
data: PackageUpdate,
|
||||
companyId: number
|
||||
): Promise<ApiResponse<Package>> {
|
||||
return api.put<Package>(`/v1/a76/packages/${packageId}?company_id=${companyId}`, data);
|
||||
export async function updatePackage(id: number, data: PackageUpdate): Promise<ApiResponse<Package>> {
|
||||
return await api.put(`/a76/packages/${id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Eliminar un package
|
||||
*/
|
||||
export async function deletePackage(
|
||||
packageId: number,
|
||||
companyId: number
|
||||
): Promise<ApiResponse<void>> {
|
||||
return api.delete<void>(`/v1/a76/packages/${packageId}?company_id=${companyId}`);
|
||||
export async function deletePackage(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/packages/${id}`);
|
||||
}
|
||||
|
||||
68
frontend/src/lib/api/dashboard/a76/ports.ts
Normal file
68
frontend/src/lib/api/dashboard/a76/ports.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export enum PortType {
|
||||
ENTRY = 'ENTRY',
|
||||
EXIT = 'EXIT',
|
||||
BOTH = 'BOTH'
|
||||
}
|
||||
|
||||
export interface Port {
|
||||
id: number;
|
||||
port_code: string;
|
||||
description: string | null;
|
||||
location_code: string;
|
||||
location_description: string | null;
|
||||
port_type: PortType;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface PortCreate {
|
||||
port_code: string;
|
||||
description?: string | null;
|
||||
location_code: string;
|
||||
location_description?: string | null;
|
||||
port_type?: PortType;
|
||||
}
|
||||
|
||||
export interface PortUpdate {
|
||||
port_code?: string;
|
||||
description?: string | null;
|
||||
location_code?: string;
|
||||
location_description?: string | null;
|
||||
port_type?: PortType;
|
||||
}
|
||||
|
||||
export interface PortListResponse {
|
||||
items: Port[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getPorts(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<PortListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/ports?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
export async function createPort(data: PortCreate): Promise<ApiResponse<Port>> {
|
||||
return await api.post('/a76/ports', data);
|
||||
}
|
||||
|
||||
export async function updatePort(id: number, data: PortUpdate): Promise<ApiResponse<Port>> {
|
||||
return await api.put(`/a76/ports/${id}`, data);
|
||||
}
|
||||
|
||||
export async function deletePort(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/ports/${id}`);
|
||||
}
|
||||
61
frontend/src/lib/api/dashboard/a76/prevalidators.ts
Normal file
61
frontend/src/lib/api/dashboard/a76/prevalidators.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Prevalidator {
|
||||
id: number;
|
||||
code: string;
|
||||
description?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface PrevalidatorCreate {
|
||||
code: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface PrevalidatorUpdate extends Partial<PrevalidatorCreate> {}
|
||||
|
||||
export interface PrevalidatorListResponse {
|
||||
items: Prevalidator[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getPrevalidators(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<PrevalidatorListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/prevalidators?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getPrevalidator(id: number): Promise<Prevalidator> {
|
||||
const response = await api.get(`/a76/prevalidators/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createPrevalidator(data: PrevalidatorCreate): Promise<Prevalidator> {
|
||||
const response = await api.post('/a76/prevalidators', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updatePrevalidator(id: number, data: PrevalidatorUpdate): Promise<Prevalidator> {
|
||||
const response = await api.patch(`/a76/prevalidators/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deletePrevalidator(id: number): Promise<void> {
|
||||
await api.delete(`/a76/prevalidators/${id}`);
|
||||
}
|
||||
63
frontend/src/lib/api/dashboard/a76/signatures.ts
Normal file
63
frontend/src/lib/api/dashboard/a76/signatures.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface Signature {
|
||||
id: number;
|
||||
name: string;
|
||||
position?: string;
|
||||
certificate?: string;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface SignatureCreate {
|
||||
name: string;
|
||||
position?: string;
|
||||
certificate?: string;
|
||||
}
|
||||
|
||||
export interface SignatureUpdate extends Partial<SignatureCreate> {}
|
||||
|
||||
export interface SignatureListResponse {
|
||||
items: Signature[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getSignatures(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<SignatureListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/signatures?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getSignature(id: number): Promise<Signature> {
|
||||
const response = await api.get(`/a76/signatures/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createSignature(data: SignatureCreate): Promise<Signature> {
|
||||
const response = await api.post('/a76/signatures', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateSignature(id: number, data: SignatureUpdate): Promise<Signature> {
|
||||
const response = await api.patch(`/a76/signatures/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteSignature(id: number): Promise<void> {
|
||||
await api.delete(`/a76/signatures/${id}`);
|
||||
}
|
||||
63
frontend/src/lib/api/dashboard/a76/unit-conversions.ts
Normal file
63
frontend/src/lib/api/dashboard/a76/unit-conversions.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
export interface UnitConversion {
|
||||
id: number;
|
||||
from_unit_id: number;
|
||||
to_unit_id: number;
|
||||
conversion_factor: number;
|
||||
tenant_id: string;
|
||||
company_id?: string;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface UnitConversionCreate {
|
||||
from_unit_id: number;
|
||||
to_unit_id: number;
|
||||
conversion_factor: number;
|
||||
}
|
||||
|
||||
export interface UnitConversionUpdate extends Partial<UnitConversionCreate> {}
|
||||
|
||||
export interface UnitConversionListResponse {
|
||||
items: UnitConversion[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getUnitConversions(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<UnitConversionListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/a76/unit_conversions?${params.toString()}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function getUnitConversion(id: number): Promise<UnitConversion> {
|
||||
const response = await api.get(`/a76/unit_conversions/${id}`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function createUnitConversion(data: UnitConversionCreate): Promise<UnitConversion> {
|
||||
const response = await api.post('/a76/unit_conversions', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateUnitConversion(id: number, data: UnitConversionUpdate): Promise<UnitConversion> {
|
||||
const response = await api.patch(`/a76/unit_conversions/${id}`, data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function deleteUnitConversion(id: number): Promise<void> {
|
||||
await api.delete(`/a76/unit_conversions/${id}`);
|
||||
}
|
||||
158
frontend/src/lib/api/dashboard/a76/units-of-measure.ts
Normal file
158
frontend/src/lib/api/dashboard/a76/units-of-measure.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
|
||||
// --- ACE ---
|
||||
export interface UnitOfMeasureACE {
|
||||
id: number;
|
||||
code: string;
|
||||
description: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureACECreate {
|
||||
code: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureACEUpdate {
|
||||
code?: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureACEListResponse {
|
||||
items: UnitOfMeasureACE[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getUnitsOfMeasureACE(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<UnitOfMeasureACEListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/units-of-measure/ace?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
export async function createUnitOfMeasureACE(data: UnitOfMeasureACECreate): Promise<ApiResponse<UnitOfMeasureACE>> {
|
||||
return await api.post('/a76/units-of-measure/ace', data);
|
||||
}
|
||||
|
||||
export async function updateUnitOfMeasureACE(id: number, data: UnitOfMeasureACEUpdate): Promise<ApiResponse<UnitOfMeasureACE>> {
|
||||
return await api.put(`/a76/units-of-measure/ace/${id}`, data);
|
||||
}
|
||||
|
||||
export async function deleteUnitOfMeasureACE(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/units-of-measure/ace/${id}`);
|
||||
}
|
||||
|
||||
// --- OMA ---
|
||||
export interface UnitOfMeasureOMA {
|
||||
id: number;
|
||||
code: string;
|
||||
description: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureOMACreate {
|
||||
code: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureOMAUpdate {
|
||||
code?: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureOMAListResponse {
|
||||
items: UnitOfMeasureOMA[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getUnitsOfMeasureOMA(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<UnitOfMeasureOMAListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/units-of-measure/oma?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
export async function createUnitOfMeasureOMA(data: UnitOfMeasureOMACreate): Promise<ApiResponse<UnitOfMeasureOMA>> {
|
||||
return await api.post('/a76/units-of-measure/oma', data);
|
||||
}
|
||||
|
||||
export async function updateUnitOfMeasureOMA(id: number, data: UnitOfMeasureOMAUpdate): Promise<ApiResponse<UnitOfMeasureOMA>> {
|
||||
return await api.put(`/a76/units-of-measure/oma/${id}`, data);
|
||||
}
|
||||
|
||||
export async function deleteUnitOfMeasureOMA(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/units-of-measure/oma/${id}`);
|
||||
}
|
||||
|
||||
// --- American ---
|
||||
export interface UnitOfMeasureAmerican {
|
||||
id: number;
|
||||
code: string;
|
||||
description: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureAmericanCreate {
|
||||
code: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureAmericanUpdate {
|
||||
code?: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureAmericanListResponse {
|
||||
items: UnitOfMeasureAmerican[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getUnitsOfMeasureAmerican(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<UnitOfMeasureAmericanListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/units-of-measure/american?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
export async function createUnitOfMeasureAmerican(data: UnitOfMeasureAmericanCreate): Promise<ApiResponse<UnitOfMeasureAmerican>> {
|
||||
return await api.post('/a76/units-of-measure/american', data);
|
||||
}
|
||||
|
||||
export async function updateUnitOfMeasureAmerican(id: number, data: UnitOfMeasureAmericanUpdate): Promise<ApiResponse<UnitOfMeasureAmerican>> {
|
||||
return await api.put(`/a76/units-of-measure/american/${id}`, data);
|
||||
}
|
||||
|
||||
export async function deleteUnitOfMeasureAmerican(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/units-of-measure/american/${id}`);
|
||||
}
|
||||
38
frontend/src/lib/components/dashboard/company/columns.ts
Normal file
38
frontend/src/lib/components/dashboard/company/columns.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { Company } from '$lib/api/dashboard/a76/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>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'name',
|
||||
header: 'Nombre',
|
||||
cell: ({ row }) => row.original.name || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'rfc',
|
||||
header: 'RFC',
|
||||
cell: ({ row }) => row.original.rfc || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'program',
|
||||
header: 'Programa',
|
||||
cell: ({ row }) => row.original.program || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'program_number',
|
||||
header: 'No. Programa',
|
||||
cell: ({ row }) => row.original.program_number || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { createCompany, updateCompany, type Company } from "$lib/api/dashboard/a76/company";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: Company | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(!!item);
|
||||
const title = $derived(isEdit ? "Editar Empresa" : "Nueva Empresa");
|
||||
|
||||
let formData = $state({
|
||||
name: item?.name || '',
|
||||
rfc: item?.rfc || '',
|
||||
main_activity: item?.main_activity || '',
|
||||
program: item?.program || '',
|
||||
program_number: item?.program_number || ''
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (item) {
|
||||
formData = {
|
||||
name: item.name || '',
|
||||
rfc: item.rfc || '',
|
||||
main_activity: item.main_activity || '',
|
||||
program: item.program || '',
|
||||
program_number: item.program_number || ''
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
name: '',
|
||||
rfc: '',
|
||||
main_activity: '',
|
||||
program: '',
|
||||
program_number: ''
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
error = null;
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
if (!formData.name.trim()) throw new Error('El nombre es requerido');
|
||||
|
||||
const dataToSend = {
|
||||
name: formData.name.trim(),
|
||||
rfc: formData.rfc.trim() || null,
|
||||
main_activity: formData.main_activity.trim() || null,
|
||||
program: formData.program.trim() || null,
|
||||
program_number: formData.program_number.trim() || null
|
||||
};
|
||||
|
||||
let response;
|
||||
if (isEdit && item) {
|
||||
response = await updateCompany(item.id, dataToSend);
|
||||
} else {
|
||||
response = await createCompany(dataToSend);
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Error al guardar';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="max-w-md">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleSubmit(); }} class="space-y-4">
|
||||
{#if error}
|
||||
<div class="text-destructive text-sm">{error}</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="name">Nombre <span class="text-destructive">*</span></Label>
|
||||
<Input id="name" bind:value={formData.name} />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="rfc">RFC</Label>
|
||||
<Input id="rfc" bind:value={formData.rfc} />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="program">Programa</Label>
|
||||
<Input id="program" bind:value={formData.program} />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="program_number">No. Programa</Label>
|
||||
<Input id="program_number" bind:value={formData.program_number} />
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => open = false}>Cancelar</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Guardando...' : 'Guardar'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,78 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { deleteCompany, type Company } from "$lib/api/dashboard/a76/company";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: Company;
|
||||
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 empresa "${item.name}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteCompany(item.id);
|
||||
|
||||
if (response.error) {
|
||||
alert(`Error al eliminar: ${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Error 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}
|
||||
/>
|
||||
@@ -0,0 +1,93 @@
|
||||
<script lang="ts" generics="T extends Record<string, any>">
|
||||
import * as Table from "$lib/components/ui/table/index.js";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
|
||||
type Column = {
|
||||
key: string;
|
||||
label: string;
|
||||
format?: (value: any, row: T) => string;
|
||||
};
|
||||
|
||||
type SimpleDataTableProps<T> = {
|
||||
columns: Column[];
|
||||
data: T[];
|
||||
pageCount: number;
|
||||
totalItems: number;
|
||||
};
|
||||
|
||||
let {
|
||||
data,
|
||||
columns,
|
||||
pageCount,
|
||||
totalItems
|
||||
}: SimpleDataTableProps<T> = $props();
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url);
|
||||
}
|
||||
|
||||
function getCellValue(row: T, column: Column): string {
|
||||
const value = row[column.key];
|
||||
if (column.format) {
|
||||
return column.format(value, row);
|
||||
}
|
||||
return value !== null && value !== undefined ? String(value) : '-';
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
{#each columns as column (column.key)}
|
||||
<Table.Head>{column.label}</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each data as row, i (i)}
|
||||
<Table.Row>
|
||||
{#each columns as column (column.key)}
|
||||
<Table.Cell>
|
||||
{getCellValue(row, column)}
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems}
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(Number($page.url.searchParams.get('page') || 1) - 1)}
|
||||
disabled={Number($page.url.searchParams.get('page') || 1) <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(Number($page.url.searchParams.get('page') || 1) + 1)}
|
||||
disabled={Number($page.url.searchParams.get('page') || 1) >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
37
frontend/src/lib/components/dashboard/identifiers/columns.ts
Normal file
37
frontend/src/lib/components/dashboard/identifiers/columns.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { Identifier } from '$lib/api/dashboard/a76/identifiers';
|
||||
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<Identifier>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
header: 'Clave',
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'level',
|
||||
header: 'Nivel',
|
||||
cell: ({ row }) => row.original.level || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'complement',
|
||||
header: 'Complemento',
|
||||
cell: ({ row }) => row.original.complement || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { Textarea } from "$lib/components/ui/textarea";
|
||||
import { createIdentifier, updateIdentifier, type Identifier } from "$lib/api/dashboard/a76/identifiers";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
mode = 'create',
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
mode?: 'create' | 'edit';
|
||||
item?: Identifier | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(mode === 'edit');
|
||||
const title = $derived(isEdit ? "Editar Identificador" : "Nuevo Identificador");
|
||||
|
||||
let formData = $state({
|
||||
code: '',
|
||||
description: '',
|
||||
level: '',
|
||||
complement: ''
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (isEdit && item) {
|
||||
formData = {
|
||||
code: item.code,
|
||||
description: item.description || '',
|
||||
level: item.level || '',
|
||||
complement: item.complement || ''
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
code: '',
|
||||
description: '',
|
||||
level: '',
|
||||
complement: ''
|
||||
};
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
error = 'No hay compañía seleccionada';
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
let response;
|
||||
if (isEdit && item) {
|
||||
response = await updateIdentifier(item.id, {
|
||||
code: formData.code,
|
||||
description: formData.description || null,
|
||||
level: formData.level || null,
|
||||
complement: formData.complement || null
|
||||
});
|
||||
} else {
|
||||
response = await createIdentifier({
|
||||
code: formData.code,
|
||||
description: formData.description || null,
|
||||
level: formData.level || null,
|
||||
complement: formData.complement || null,
|
||||
company_id: companyId
|
||||
});
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
return;
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = 'Error de conexión';
|
||||
console.error(e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[425px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<div class="grid gap-4 py-4">
|
||||
{#if error}
|
||||
<div class="text-red-500 text-sm mb-2">{error}</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="code" class="text-right">Clave</Label>
|
||||
<Input id="code" bind:value={formData.code} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="description" class="text-right">Descripción</Label>
|
||||
<Textarea id="description" bind:value={formData.description} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="level" class="text-right">Nivel</Label>
|
||||
<Input id="level" bind:value={formData.level} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="complement" class="text-right">Complemento</Label>
|
||||
<Textarea id="complement" bind:value={formData.complement} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="submit" onclick={handleSubmit} disabled={loading}>
|
||||
{loading ? 'Guardando...' : 'Guardar'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,79 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { deleteIdentifier, type Identifier } from "$lib/api/dashboard/a76/identifiers";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-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);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm(`¿Estás seguro de eliminar el identificador "${item.code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteIdentifier(item.id);
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
alert(`Error al eliminar: ${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = 'Error de conexión';
|
||||
console.error(e);
|
||||
alert('Error de conexión al eliminar');
|
||||
} 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.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}
|
||||
mode="edit"
|
||||
{item}
|
||||
{onSuccess}
|
||||
/>
|
||||
@@ -0,0 +1,106 @@
|
||||
<script lang="ts" generics="TData, TValue">
|
||||
import {
|
||||
type ColumnDef,
|
||||
getCoreRowModel,
|
||||
} from "@tanstack/table-core";
|
||||
import { createSvelteTable, FlexRender } from "$lib/components/ui/data-table/index.js";
|
||||
import * as Table from "$lib/components/ui/table/index.js";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
|
||||
type DataTableProps<TData, TValue> = {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
data: TData[];
|
||||
pageCount: number;
|
||||
totalItems: number;
|
||||
};
|
||||
|
||||
let {
|
||||
data,
|
||||
columns,
|
||||
pageCount,
|
||||
totalItems
|
||||
}: DataTableProps<TData, TValue> = $props();
|
||||
|
||||
const table = createSvelteTable({
|
||||
get data() {
|
||||
return data;
|
||||
},
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
pageCount: pageCount,
|
||||
});
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems}
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(Number($page.url.searchParams.get('page') || 1) - 1)}
|
||||
disabled={Number($page.url.searchParams.get('page') || 1) <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(Number($page.url.searchParams.get('page') || 1) + 1)}
|
||||
disabled={Number($page.url.searchParams.get('page') || 1) >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -95,9 +95,9 @@
|
||||
|
||||
let response;
|
||||
if (isEdit && item) {
|
||||
response = await updatePackage(item.id, dataToSend as PackageUpdate, companyId);
|
||||
response = await updatePackage(item.id, dataToSend);
|
||||
} else {
|
||||
response = await createPackage(dataToSend as PackageCreate, companyId);
|
||||
response = await createPackage({ ...dataToSend, company_id: companyId });
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
|
||||
let {
|
||||
package: item,
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
package: Package;
|
||||
item: Package;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
@@ -24,17 +24,11 @@
|
||||
return;
|
||||
}
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
alert('No hay compañía seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deletePackage(item.id, companyId);
|
||||
const response = await deletePackage(item.id);
|
||||
|
||||
if (response.error) {
|
||||
if (response.status === 401) {
|
||||
|
||||
47
frontend/src/lib/components/dashboard/ports/columns.ts
Normal file
47
frontend/src/lib/components/dashboard/ports/columns.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import type { Port } from '$lib/api/dashboard/a76/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>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'port_code',
|
||||
header: 'Código Puerto',
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'location_code',
|
||||
header: 'Código Ubicación',
|
||||
},
|
||||
{
|
||||
accessorKey: 'location_description',
|
||||
header: 'Ubicación',
|
||||
cell: ({ row }) => row.original.location_description || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'port_type',
|
||||
header: 'Tipo',
|
||||
cell: ({ row }) => {
|
||||
const type = row.original.port_type;
|
||||
if (type === 'ENTRY') return 'Entrada';
|
||||
if (type === 'EXIT') return 'Salida';
|
||||
if (type === 'BOTH') return 'Ambos';
|
||||
return type;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import * as Select from "$lib/components/ui/select";
|
||||
import { createPort, updatePort, type Port, PortType } from "$lib/api/dashboard/a76/ports";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
mode = 'create',
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
mode?: 'create' | 'edit';
|
||||
item?: Port | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(mode === 'edit');
|
||||
const title = $derived(isEdit ? "Editar Puerto" : "Nuevo Puerto");
|
||||
|
||||
let formData = $state({
|
||||
port_code: '',
|
||||
description: '',
|
||||
location_code: '',
|
||||
location_description: '',
|
||||
port_type: PortType.ENTRY
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (isEdit && item) {
|
||||
formData = {
|
||||
port_code: item.port_code,
|
||||
description: item.description || '',
|
||||
location_code: item.location_code,
|
||||
location_description: item.location_description || '',
|
||||
port_type: item.port_type
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
port_code: '',
|
||||
description: '',
|
||||
location_code: '',
|
||||
location_description: '',
|
||||
port_type: PortType.ENTRY
|
||||
};
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
let response;
|
||||
if (isEdit && item) {
|
||||
response = await updatePort(item.id, {
|
||||
port_code: formData.port_code,
|
||||
description: formData.description || null,
|
||||
location_code: formData.location_code,
|
||||
location_description: formData.location_description || null,
|
||||
port_type: formData.port_type
|
||||
});
|
||||
} else {
|
||||
response = await createPort({
|
||||
port_code: formData.port_code,
|
||||
description: formData.description || null,
|
||||
location_code: formData.location_code,
|
||||
location_description: formData.location_description || null,
|
||||
port_type: formData.port_type
|
||||
});
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
return;
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = 'Error de conexión';
|
||||
console.error(e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[425px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<div class="grid gap-4 py-4">
|
||||
{#if error}
|
||||
<div class="text-red-500 text-sm mb-2">{error}</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="port_code" class="text-right">Código Puerto</Label>
|
||||
<Input id="port_code" bind:value={formData.port_code} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="description" class="text-right">Descripción</Label>
|
||||
<Input id="description" bind:value={formData.description} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="location_code" class="text-right">Código Ubicación</Label>
|
||||
<Input id="location_code" bind:value={formData.location_code} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="location_description" class="text-right">Ubicación</Label>
|
||||
<Input id="location_description" bind:value={formData.location_description} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="port_type" class="text-right">Tipo</Label>
|
||||
<div class="col-span-3">
|
||||
<Select.Root type="single" bind:value={formData.port_type}>
|
||||
<Select.Trigger>
|
||||
{formData.port_type === 'ENTRY' ? 'Entrada' :
|
||||
formData.port_type === 'EXIT' ? 'Salida' :
|
||||
formData.port_type === 'BOTH' ? 'Ambos' : 'Seleccionar'}
|
||||
</Select.Trigger>
|
||||
<Select.Content>
|
||||
<Select.Item value="ENTRY">Entrada</Select.Item>
|
||||
<Select.Item value="EXIT">Salida</Select.Item>
|
||||
<Select.Item value="BOTH">Ambos</Select.Item>
|
||||
</Select.Content>
|
||||
</Select.Root>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="submit" onclick={handleSubmit} disabled={loading}>
|
||||
{loading ? 'Guardando...' : 'Guardar'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,79 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { deletePort, type Port } from "$lib/api/dashboard/a76/ports";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: Port;
|
||||
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 puerto "${item.port_code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deletePort(item.id);
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
alert(`Error al eliminar: ${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = 'Error de conexión';
|
||||
console.error(e);
|
||||
alert('Error de conexión al eliminar');
|
||||
} 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.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}
|
||||
mode="edit"
|
||||
{item}
|
||||
{onSuccess}
|
||||
/>
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { UnitOfMeasureACE } from '$lib/api/dashboard/a76/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>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
header: 'Código',
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { createUnitOfMeasureACE, updateUnitOfMeasureACE, type UnitOfMeasureACE } from "$lib/api/dashboard/a76/units-of-measure";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
mode = 'create',
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
mode?: 'create' | 'edit';
|
||||
item?: UnitOfMeasureACE | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(mode === 'edit');
|
||||
const title = $derived(isEdit ? "Editar Unidad ACE" : "Nueva Unidad ACE");
|
||||
|
||||
let formData = $state({
|
||||
code: '',
|
||||
description: ''
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (isEdit && item) {
|
||||
formData = {
|
||||
code: item.code,
|
||||
description: item.description || ''
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
code: '',
|
||||
description: ''
|
||||
};
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
let response;
|
||||
if (isEdit && item) {
|
||||
response = await updateUnitOfMeasureACE(item.id, {
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
});
|
||||
} else {
|
||||
response = await createUnitOfMeasureACE({
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
});
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
return;
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = 'Error de conexión';
|
||||
console.error(e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[425px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<div class="grid gap-4 py-4">
|
||||
{#if error}
|
||||
<div class="text-red-500 text-sm mb-2">{error}</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="code" class="text-right">Código</Label>
|
||||
<Input id="code" bind:value={formData.code} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="description" class="text-right">Descripción</Label>
|
||||
<Input id="description" bind:value={formData.description} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="submit" onclick={handleSubmit} disabled={loading}>
|
||||
{loading ? 'Guardando...' : 'Guardar'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,79 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { deleteUnitOfMeasureACE, type UnitOfMeasureACE } from "$lib/api/dashboard/a76/units-of-measure";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: UnitOfMeasureACE;
|
||||
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 unidad "${item.code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteUnitOfMeasureACE(item.id);
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
alert(`Error al eliminar: ${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = 'Error de conexión';
|
||||
console.error(e);
|
||||
alert('Error de conexión al eliminar');
|
||||
} 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.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}
|
||||
mode="edit"
|
||||
{item}
|
||||
{onSuccess}
|
||||
/>
|
||||
@@ -0,0 +1,106 @@
|
||||
<script lang="ts" generics="TData, TValue">
|
||||
import {
|
||||
type ColumnDef,
|
||||
getCoreRowModel,
|
||||
} from "@tanstack/table-core";
|
||||
import { createSvelteTable, FlexRender } from "$lib/components/ui/data-table/index.js";
|
||||
import * as Table from "$lib/components/ui/table/index.js";
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import { goto } from "$app/navigation";
|
||||
import { page } from "$app/stores";
|
||||
|
||||
type DataTableProps<TData, TValue> = {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
data: TData[];
|
||||
pageCount: number;
|
||||
totalItems: number;
|
||||
};
|
||||
|
||||
let {
|
||||
data,
|
||||
columns,
|
||||
pageCount,
|
||||
totalItems
|
||||
}: DataTableProps<TData, TValue> = $props();
|
||||
|
||||
const table = createSvelteTable({
|
||||
get data() {
|
||||
return data;
|
||||
},
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
pageCount: pageCount,
|
||||
});
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems}
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(Number($page.url.searchParams.get('page') || 1) - 1)}
|
||||
disabled={Number($page.url.searchParams.get('page') || 1) <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(Number($page.url.searchParams.get('page') || 1) + 1)}
|
||||
disabled={Number($page.url.searchParams.get('page') || 1) >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { UnitOfMeasureAmerican } from '$lib/api/dashboard/a76/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<UnitOfMeasureAmerican>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
header: 'Código',
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { createUnitOfMeasureAmerican, updateUnitOfMeasureAmerican, type UnitOfMeasureAmerican } from "$lib/api/dashboard/a76/units-of-measure";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
mode = 'create',
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
mode?: 'create' | 'edit';
|
||||
item?: UnitOfMeasureAmerican | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(mode === 'edit');
|
||||
const title = $derived(isEdit ? "Editar Unidad Americana" : "Nueva Unidad Americana");
|
||||
|
||||
let formData = $state({
|
||||
code: '',
|
||||
description: ''
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (isEdit && item) {
|
||||
formData = {
|
||||
code: item.code,
|
||||
description: item.description || ''
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
code: '',
|
||||
description: ''
|
||||
};
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
let response;
|
||||
if (isEdit && item) {
|
||||
response = await updateUnitOfMeasureAmerican(item.id, {
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
});
|
||||
} else {
|
||||
response = await createUnitOfMeasureAmerican({
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
});
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
return;
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = 'Error de conexión';
|
||||
console.error(e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[425px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<div class="grid gap-4 py-4">
|
||||
{#if error}
|
||||
<div class="text-red-500 text-sm mb-2">{error}</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="code" class="text-right">Código</Label>
|
||||
<Input id="code" bind:value={formData.code} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="description" class="text-right">Descripción</Label>
|
||||
<Input id="description" bind:value={formData.description} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="submit" onclick={handleSubmit} disabled={loading}>
|
||||
{loading ? 'Guardando...' : 'Guardar'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,79 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { deleteUnitOfMeasureAmerican, type UnitOfMeasureAmerican } from "$lib/api/dashboard/a76/units-of-measure";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: UnitOfMeasureAmerican;
|
||||
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 unidad "${item.code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteUnitOfMeasureAmerican(item.id);
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
alert(`Error al eliminar: ${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = 'Error de conexión';
|
||||
console.error(e);
|
||||
alert('Error de conexión al eliminar');
|
||||
} 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.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}
|
||||
mode="edit"
|
||||
{item}
|
||||
{onSuccess}
|
||||
/>
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { UnitOfMeasureOMA } from '$lib/api/dashboard/a76/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<UnitOfMeasureOMA>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
header: 'Código',
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import { Input } from "$lib/components/ui/input";
|
||||
import { Label } from "$lib/components/ui/label";
|
||||
import { createUnitOfMeasureOMA, updateUnitOfMeasureOMA, type UnitOfMeasureOMA } from "$lib/api/dashboard/a76/units-of-measure";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
mode = 'create',
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
mode?: 'create' | 'edit';
|
||||
item?: UnitOfMeasureOMA | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(mode === 'edit');
|
||||
const title = $derived(isEdit ? "Editar Unidad OMA" : "Nueva Unidad OMA");
|
||||
|
||||
let formData = $state({
|
||||
code: '',
|
||||
description: ''
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (isEdit && item) {
|
||||
formData = {
|
||||
code: item.code,
|
||||
description: item.description || ''
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
code: '',
|
||||
description: ''
|
||||
};
|
||||
}
|
||||
error = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
let response;
|
||||
if (isEdit && item) {
|
||||
response = await updateUnitOfMeasureOMA(item.id, {
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
});
|
||||
} else {
|
||||
response = await createUnitOfMeasureOMA({
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
});
|
||||
}
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
return;
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = 'Error de conexión';
|
||||
console.error(e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[425px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{title}</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<div class="grid gap-4 py-4">
|
||||
{#if error}
|
||||
<div class="text-red-500 text-sm mb-2">{error}</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="code" class="text-right">Código</Label>
|
||||
<Input id="code" bind:value={formData.code} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label for="description" class="text-right">Descripción</Label>
|
||||
<Input id="description" bind:value={formData.description} class="col-span-3" disabled={loading} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="submit" onclick={handleSubmit} disabled={loading}>
|
||||
{loading ? 'Guardando...' : 'Guardar'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,79 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { deleteUnitOfMeasureOMA, type UnitOfMeasureOMA } from "$lib/api/dashboard/a76/units-of-measure";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: UnitOfMeasureOMA;
|
||||
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 unidad "${item.code}"?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteUnitOfMeasureOMA(item.id);
|
||||
|
||||
if (response.error) {
|
||||
error = response.error;
|
||||
alert(`Error al eliminar: ${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (onSuccess) onSuccess();
|
||||
} catch (e) {
|
||||
error = 'Error de conexión';
|
||||
console.error(e);
|
||||
alert('Error de conexión al eliminar');
|
||||
} 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.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}
|
||||
mode="edit"
|
||||
{item}
|
||||
{onSuccess}
|
||||
/>
|
||||
@@ -155,7 +155,7 @@ export function getSidebarData(): SidebarData {
|
||||
items: [
|
||||
{
|
||||
title: m["sidebar.general_catalogs.company_information"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/company_information",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.packages"](),
|
||||
@@ -163,15 +163,19 @@ export function getSidebarData(): SidebarData {
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.concepts"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/concepts",
|
||||
},
|
||||
{
|
||||
title: "Conceptos de Agente Aduanal",
|
||||
url: "/dashboard/general_catalogs/customs_broker_concepts",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.classification"](),
|
||||
url: "/dashboard/general_catalogs/classes",
|
||||
url: "/dashboard/general_catalogs/classification_concepts",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.identifiers"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/identifiers",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.incoterms"](),
|
||||
@@ -179,11 +183,11 @@ export function getSidebarData(): SidebarData {
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.inpc"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/inpc",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.fixed_legends"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/legends",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.seals"](),
|
||||
@@ -199,11 +203,11 @@ export function getSidebarData(): SidebarData {
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.ports"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/ports",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.unit_measures"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/units_of_measures",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.um_customs_mex"](),
|
||||
@@ -223,11 +227,11 @@ export function getSidebarData(): SidebarData {
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.conversions"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/unit_conversions",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.equivalences"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/equivalencies",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.exchange_rates"](),
|
||||
@@ -239,7 +243,7 @@ export function getSidebarData(): SidebarData {
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.multi_currency"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/multi_currency_types",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.invoice_types"](),
|
||||
@@ -247,11 +251,11 @@ export function getSidebarData(): SidebarData {
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.electronic_signatures"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/signatures",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.billing_errors"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/error_catalogs",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.customs_warehouses"](),
|
||||
@@ -263,7 +267,7 @@ export function getSidebarData(): SidebarData {
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.doda"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/doda",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.packing_list"](),
|
||||
@@ -271,11 +275,11 @@ export function getSidebarData(): SidebarData {
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.prevalidators"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/prevalidators",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.electronic_notices"](),
|
||||
url: "#",
|
||||
url: "/dashboard/general_catalogs/electronic_notices",
|
||||
},
|
||||
{
|
||||
title: m["sidebar.general_catalogs.back_flush"](),
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { getClassificationConcepts } from '$lib/api/dashboard/a76/classification-concepts';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const classification = url.searchParams.get('classification');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (classification) filters.classification = classification;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const response = await getClassificationConcepts(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
classifications: response.data
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
// Filtros
|
||||
let searchClassification = $state($page.url.searchParams.get('classification') || '');
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = [
|
||||
{ key: 'classification', label: 'Clasificación' },
|
||||
{ key: 'description', label: 'Descripción' },
|
||||
];
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
if (searchClassification) url.searchParams.set('classification', searchClassification);
|
||||
else url.searchParams.delete('classification');
|
||||
|
||||
if (searchDesc) url.searchParams.set('description', searchDesc);
|
||||
else url.searchParams.delete('description');
|
||||
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Clasificaciones de Conceptos</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestión del catálogo de clasificaciones de conceptos
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Clasificación
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por clasificación..."
|
||||
bind:value={searchClassification}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por descripción..."
|
||||
bind:value={searchDesc}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<SimpleDataTable
|
||||
data={data.classifications?.items || []}
|
||||
columns={columns}
|
||||
pageCount={data.classifications?.pages || 0}
|
||||
totalItems={data.classifications?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { getCompanies } from '$lib/api/dashboard/a76/company';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const name = url.searchParams.get('name');
|
||||
const rfc = url.searchParams.get('rfc');
|
||||
|
||||
if (name) filters.name = name;
|
||||
if (rfc) filters.rfc = rfc;
|
||||
|
||||
const response = await getCompanies(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
companies: response.data
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import DataTable from '$lib/components/dashboard/units_of_measure/ace/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/company/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/company/create-edit-dialog.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import * as m from "$lib/paraglide/messages.js";
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
// Filtros
|
||||
let searchName = $state($page.url.searchParams.get('name') || '');
|
||||
let searchRfc = $state($page.url.searchParams.get('rfc') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
if (searchName) url.searchParams.set('name', searchName);
|
||||
else url.searchParams.delete('name');
|
||||
|
||||
if (searchRfc) url.searchParams.set('rfc', searchRfc);
|
||||
else url.searchParams.delete('rfc');
|
||||
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">company_information</h1> <!-- {m["sidebar.general_catalogs.company_information"]()} -->
|
||||
<p class="text-muted-foreground">
|
||||
Gestión de información de empresas
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Empresa
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por nombre..."
|
||||
bind:value={searchName}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por RFC..."
|
||||
bind:value={searchRfc}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<DataTable
|
||||
data={data.companies?.items || []}
|
||||
columns={createColumns(handleSuccess)}
|
||||
pageCount={data.companies?.pages || 0}
|
||||
totalItems={data.companies?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { getConcepts } from '$lib/api/dashboard/a76/concepts';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const response = await getConcepts(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
concepts: response.data
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
// Filtros
|
||||
let searchCode = $state($page.url.searchParams.get('code') || '');
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = [
|
||||
{ key: 'code', label: 'Código' },
|
||||
{ key: 'description', label: 'Descripción' },
|
||||
{ key: 'type', label: 'Tipo' },
|
||||
];
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
if (searchCode) url.searchParams.set('code', searchCode);
|
||||
else url.searchParams.delete('code');
|
||||
|
||||
if (searchDesc) url.searchParams.set('description', searchDesc);
|
||||
else url.searchParams.delete('description');
|
||||
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Conceptos</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestión del catálogo de conceptos
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Concepto
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por código..."
|
||||
bind:value={searchCode}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por descripción..."
|
||||
bind:value={searchDesc}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<SimpleDataTable
|
||||
data={data.concepts?.items || []}
|
||||
columns={columns}
|
||||
pageCount={data.concepts?.pages || 0}
|
||||
totalItems={data.concepts?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { getCustomsBrokerConcepts } from '$lib/api/dashboard/a76/customs-broker-concepts';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const response = await getCustomsBrokerConcepts(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
concepts: response.data
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
// Filtros
|
||||
let searchCode = $state($page.url.searchParams.get('code') || '');
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = [
|
||||
{ key: 'code', label: 'Código' },
|
||||
{ key: 'description', label: 'Descripción' },
|
||||
{ key: 'type', label: 'Tipo' },
|
||||
];
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
if (searchCode) url.searchParams.set('code', searchCode);
|
||||
else url.searchParams.delete('code');
|
||||
|
||||
if (searchDesc) url.searchParams.set('description', searchDesc);
|
||||
else url.searchParams.delete('description');
|
||||
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Conceptos de Agente Aduanal</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestión del catálogo de conceptos de agente aduanal
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Concepto
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por código..."
|
||||
bind:value={searchCode}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por descripción..."
|
||||
bind:value={searchDesc}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<SimpleDataTable
|
||||
data={data.concepts?.items || []}
|
||||
columns={columns}
|
||||
pageCount={data.concepts?.pages || 0}
|
||||
totalItems={data.concepts?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { getDODAs } from '$lib/api/dashboard/a76/doda';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const response = await getDODAs(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
dodas: response.data
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
// Filtros
|
||||
let searchCode = $state($page.url.searchParams.get('code') || '');
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = [
|
||||
{ key: 'code', label: 'Código' },
|
||||
{ key: 'description', label: 'Descripción' },
|
||||
];
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
if (searchCode) url.searchParams.set('code', searchCode);
|
||||
else url.searchParams.delete('code');
|
||||
|
||||
if (searchDesc) url.searchParams.set('description', searchDesc);
|
||||
else url.searchParams.delete('description');
|
||||
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">DODA</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestión del catálogo de DODA
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo DODA
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por código..."
|
||||
bind:value={searchCode}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por descripción..."
|
||||
bind:value={searchDesc}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<SimpleDataTable
|
||||
data={data.dodas?.items || []}
|
||||
columns={columns}
|
||||
pageCount={data.dodas?.pages || 0}
|
||||
totalItems={data.dodas?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { getElectronicNotices } from '$lib/api/dashboard/a76/electronic-notices';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const response = await getElectronicNotices(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
notices: response.data
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
// Filtros
|
||||
let searchCode = $state($page.url.searchParams.get('code') || '');
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = [
|
||||
{ key: 'code', label: 'Código' },
|
||||
{ key: 'description', label: 'Descripción' },
|
||||
];
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
if (searchCode) url.searchParams.set('code', searchCode);
|
||||
else url.searchParams.delete('code');
|
||||
|
||||
if (searchDesc) url.searchParams.set('description', searchDesc);
|
||||
else url.searchParams.delete('description');
|
||||
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<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>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Aviso
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por código..."
|
||||
bind:value={searchCode}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por descripción..."
|
||||
bind:value={searchDesc}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<SimpleDataTable
|
||||
data={data.notices?.items || []}
|
||||
columns={columns}
|
||||
pageCount={data.notices?.pages || 0}
|
||||
totalItems={data.notices?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { getEquivalencies } from '$lib/api/dashboard/a76/equivalencies';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const fraccion_mex = url.searchParams.get('fraccion_mex');
|
||||
const fraccion_us = url.searchParams.get('fraccion_us');
|
||||
|
||||
if (fraccion_mex) filters.fraccion_mex = fraccion_mex;
|
||||
if (fraccion_us) filters.fraccion_us = fraccion_us;
|
||||
|
||||
const response = await getEquivalencies(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
equivalencies: response.data
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
// Filtros
|
||||
let searchMex = $state($page.url.searchParams.get('fraccion_mex') || '');
|
||||
let searchUS = $state($page.url.searchParams.get('fraccion_us') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = [
|
||||
{ key: 'fraccion_mex', label: 'Fracción MEX' },
|
||||
{ key: 'fraccion_us', label: 'Fracción US' },
|
||||
{ key: 'description', label: 'Descripción' },
|
||||
];
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
if (searchMex) url.searchParams.set('fraccion_mex', searchMex);
|
||||
else url.searchParams.delete('fraccion_mex');
|
||||
|
||||
if (searchUS) url.searchParams.set('fraccion_us', searchUS);
|
||||
else url.searchParams.delete('fraccion_us');
|
||||
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Equivalencias Arancelarias</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestión del catálogo de equivalencias entre fracciones arancelarias
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Equivalencia
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por fracción MEX..."
|
||||
bind:value={searchMex}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por fracción US..."
|
||||
bind:value={searchUS}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<SimpleDataTable
|
||||
data={data.equivalencies?.items || []}
|
||||
columns={columns}
|
||||
pageCount={data.equivalencies?.pages || 0}
|
||||
totalItems={data.equivalencies?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { getErrorCatalogs } from '$lib/api/dashboard/a76/error-catalogs';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const response = await getErrorCatalogs(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
errors: response.data
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
// Filtros
|
||||
let searchCode = $state($page.url.searchParams.get('code') || '');
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = [
|
||||
{ key: 'code', label: 'Código' },
|
||||
{ key: 'description', label: 'Descripción' },
|
||||
];
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
if (searchCode) url.searchParams.set('code', searchCode);
|
||||
else url.searchParams.delete('code');
|
||||
|
||||
if (searchDesc) url.searchParams.set('description', searchDesc);
|
||||
else url.searchParams.delete('description');
|
||||
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<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>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Error
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por código..."
|
||||
bind:value={searchCode}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por descripción..."
|
||||
bind:value={searchDesc}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<SimpleDataTable
|
||||
data={data.errors?.items || []}
|
||||
columns={columns}
|
||||
pageCount={data.errors?.pages || 0}
|
||||
totalItems={data.errors?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,56 @@
|
||||
import { getServerApiUrl, getAuthTokens } from '$lib/server/api';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url }) => {
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
throw redirect(302, '/login');
|
||||
}
|
||||
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('page_size')) || 50;
|
||||
|
||||
const apiUrl = getServerApiUrl();
|
||||
const endpoint = `${apiUrl}api/v1/a76/identifiers?page=${page}&page_size=${pageSize}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Error fetching identifiers: ${response.status} ${response.statusText}`);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: `Error: ${response.statusText}`
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
items: data.items,
|
||||
total: data.total,
|
||||
page: data.page,
|
||||
pageSize: data.page_size,
|
||||
pages: data.pages
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching identifiers:', error);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: 'Failed to connect to server'
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
<script lang="ts">
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { createColumns } from '$lib/components/dashboard/identifiers/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/identifiers/create-edit-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/identifiers/data-table.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let loading = $state(false);
|
||||
|
||||
const columns = createColumns(() => {
|
||||
refreshData();
|
||||
});
|
||||
|
||||
async function refreshData() {
|
||||
loading = true;
|
||||
await invalidateAll();
|
||||
loading = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold tracking-tight">Identificadores</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Catálogo de identificadores
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" onclick={refreshData} disabled={loading}>
|
||||
<RefreshCw class="h-4 w-4 {loading ? 'animate-spin' : ''}" />
|
||||
</Button>
|
||||
<Button onclick={() => createDialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Content class="p-0">
|
||||
<DataTable
|
||||
data={data.items}
|
||||
{columns}
|
||||
pageCount={data.pages}
|
||||
totalItems={data.total}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={refreshData}
|
||||
/>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { getINPCs } from '$lib/api/dashboard/a76/inpc';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const year = url.searchParams.get('year');
|
||||
const month = url.searchParams.get('month');
|
||||
|
||||
if (year) filters.year = year;
|
||||
if (month) filters.month = month;
|
||||
|
||||
const response = await getINPCs(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
inpcs: response.data
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
// Filtros
|
||||
let searchYear = $state($page.url.searchParams.get('year') || '');
|
||||
let searchMonth = $state($page.url.searchParams.get('month') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = [
|
||||
{ key: 'year', label: 'Año' },
|
||||
{ key: 'month', label: 'Mes' },
|
||||
{ key: 'value', label: 'Valor' },
|
||||
];
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
if (searchYear) url.searchParams.set('year', searchYear);
|
||||
else url.searchParams.delete('year');
|
||||
|
||||
if (searchMonth) url.searchParams.set('month', searchMonth);
|
||||
else url.searchParams.delete('month');
|
||||
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">INPC</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestión del catálogo de Índice Nacional de Precios al Consumidor
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo INPC
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por año..."
|
||||
bind:value={searchYear}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por mes..."
|
||||
bind:value={searchMonth}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<SimpleDataTable
|
||||
data={data.inpcs?.items || []}
|
||||
columns={columns}
|
||||
pageCount={data.inpcs?.pages || 0}
|
||||
totalItems={data.inpcs?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { getLegends } from '$lib/api/dashboard/a76/legends';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const response = await getLegends(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
legends: response.data
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
// Filtros
|
||||
let searchCode = $state($page.url.searchParams.get('code') || '');
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = [
|
||||
{ key: 'code', label: 'Código' },
|
||||
{ key: 'description', label: 'Descripción' },
|
||||
];
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
if (searchCode) url.searchParams.set('code', searchCode);
|
||||
else url.searchParams.delete('code');
|
||||
|
||||
if (searchDesc) url.searchParams.set('description', searchDesc);
|
||||
else url.searchParams.delete('description');
|
||||
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Leyendas Fijas</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestión del catálogo de leyendas fijas
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Leyenda
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por código..."
|
||||
bind:value={searchCode}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por descripción..."
|
||||
bind:value={searchDesc}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<SimpleDataTable
|
||||
data={data.legends?.items || []}
|
||||
columns={columns}
|
||||
pageCount={data.legends?.pages || 0}
|
||||
totalItems={data.legends?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { getMultiCurrencyTypes } from '$lib/api/dashboard/a76/multi-currency-types';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const key = url.searchParams.get('key');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (key) filters.key = key;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const response = await getMultiCurrencyTypes(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
types: response.data
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
// Filtros
|
||||
let searchKey = $state($page.url.searchParams.get('key') || '');
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = [
|
||||
{ key: 'key', label: 'Clave' },
|
||||
{ key: 'description', label: 'Descripción' },
|
||||
];
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
if (searchKey) url.searchParams.set('key', searchKey);
|
||||
else url.searchParams.delete('key');
|
||||
|
||||
if (searchDesc) url.searchParams.set('description', searchDesc);
|
||||
else url.searchParams.delete('description');
|
||||
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<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>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Tipo
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por clave..."
|
||||
bind:value={searchKey}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por descripción..."
|
||||
bind:value={searchDesc}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<SimpleDataTable
|
||||
data={data.types?.items || []}
|
||||
columns={columns}
|
||||
pageCount={data.types?.pages || 0}
|
||||
totalItems={data.types?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { getPackages } from '$lib/api/dashboard/a76/packages';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const key = url.searchParams.get('key');
|
||||
const description_es = url.searchParams.get('description_es');
|
||||
|
||||
if (key) filters.key = key;
|
||||
if (description_es) filters.description_es = description_es;
|
||||
|
||||
const response = await getPackages(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
packages: response.data
|
||||
};
|
||||
};
|
||||
@@ -1,326 +1,88 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { getPackages, type Package } from '$lib/api/dashboard/a76/packages';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import DataTable from '$lib/components/dashboard/packages/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/packages/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/packages/create-edit-dialog.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import { Plus, Filter, Trash2, RefreshCw } from 'lucide-svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { page } from '$app/stores';
|
||||
import DataTable from '$lib/components/dashboard/units_of_measure/ace/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/packages/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/packages/create-edit-dialog.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import * as m from "$lib/paraglide/messages.js";
|
||||
|
||||
// Estado para la lista de packages
|
||||
let allItems = $state<Package[]>([]);
|
||||
let currentPage = $state(1);
|
||||
let pageSize = $state(50);
|
||||
let totalItems = $state(0);
|
||||
let loading = $state(false);
|
||||
let hasMore = $derived(allItems.length < totalItems);
|
||||
let error = $state<string | null>(null);
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
// Filtros
|
||||
let searchKey = $state($page.url.searchParams.get('key') || '');
|
||||
let searchDesc = $state($page.url.searchParams.get('description_es') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
// Estado para filtros
|
||||
let filters = $state({
|
||||
key: '',
|
||||
description_es: ''
|
||||
});
|
||||
let showFilters = $state(false);
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
if (searchKey) url.searchParams.set('key', searchKey);
|
||||
else url.searchParams.delete('key');
|
||||
|
||||
if (searchDesc) url.searchParams.set('description_es', searchDesc);
|
||||
else url.searchParams.delete('description_es');
|
||||
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Estado para el dialog de crear
|
||||
let createDialogOpen = $state(false);
|
||||
|
||||
// Sincronizar token de cookies a localStorage al montar el componente
|
||||
onMount(() => {
|
||||
if (browser) {
|
||||
// Función para obtener el valor de una cookie
|
||||
const getCookie = (name: string): string | null => {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop()?.split(';').shift() || null;
|
||||
return null;
|
||||
};
|
||||
|
||||
// Verificar si hay token en las cookies
|
||||
const cookieToken = getCookie('access_token');
|
||||
const localToken = localStorage.getItem('access_token');
|
||||
|
||||
if (cookieToken && cookieToken !== localToken) {
|
||||
localStorage.setItem('access_token', cookieToken);
|
||||
}
|
||||
|
||||
// También sincronizar refresh_token si existe
|
||||
const cookieRefreshToken = getCookie('refresh_token');
|
||||
const localRefreshToken = localStorage.getItem('refresh_token');
|
||||
|
||||
if (cookieRefreshToken && cookieRefreshToken !== localRefreshToken) {
|
||||
localStorage.setItem('refresh_token', cookieRefreshToken);
|
||||
}
|
||||
}
|
||||
|
||||
// Esperar a que el companyStore esté inicializado antes de cargar datos
|
||||
const checkAndLoad = () => {
|
||||
if (companyStore.activeCompany) {
|
||||
loadInitialData();
|
||||
} else {
|
||||
// Si no hay compañía, esperar un poco y reintentar
|
||||
setTimeout(checkAndLoad, 100);
|
||||
}
|
||||
};
|
||||
|
||||
checkAndLoad();
|
||||
|
||||
// También escuchar cambios de compañía para recargar
|
||||
const handleCompanyChange = () => {
|
||||
loadInitialData();
|
||||
};
|
||||
|
||||
window.addEventListener('companyChanged', handleCompanyChange);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('companyChanged', handleCompanyChange);
|
||||
};
|
||||
});
|
||||
|
||||
async function loadInitialData() {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
error = 'No hay compañía seleccionada. Por favor selecciona una compañía en el sidebar.';
|
||||
loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const activeFilters = {
|
||||
...(filters.key && { key: filters.key }),
|
||||
...(filters.description_es && { description_es: filters.description_es })
|
||||
};
|
||||
|
||||
const response = await getPackages(companyId, 1, pageSize, activeFilters);
|
||||
|
||||
if (response.error) {
|
||||
console.error('📦 [Packages Page] Error en loadInitialData:', response.error);
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
error = 'Sesión expirada. Recargando página...';
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
} else {
|
||||
error = response.error;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
allItems = response.data.items;
|
||||
currentPage = response.data.page;
|
||||
totalItems = response.data.total;
|
||||
}
|
||||
} catch (e) {
|
||||
error = 'Error cargando los datos';
|
||||
console.error('📦 [Packages Page] Error loading initial data:', e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMore() {
|
||||
if (loading || !hasMore) return;
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) return;
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const activeFilters = {
|
||||
...(filters.key && { key: filters.key }),
|
||||
...(filters.description_es && { description_es: filters.description_es })
|
||||
};
|
||||
|
||||
const response = await getPackages(companyId, currentPage + 1, pageSize, activeFilters);
|
||||
|
||||
if (response.error) {
|
||||
console.error('📦 [Packages Page] Error en loadMore:', response.error);
|
||||
|
||||
if (response.status === 401 || response.status === 403) {
|
||||
error = 'Sesión expirada. Recargando página...';
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
} else {
|
||||
error = response.error;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.data) {
|
||||
allItems = [...allItems, ...response.data.items];
|
||||
currentPage = response.data.page;
|
||||
}
|
||||
} catch (e) {
|
||||
error = 'Error cargando más datos';
|
||||
console.error('📦 [Packages Page] Error loading more:', e);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleCreateSuccess() {
|
||||
createDialogOpen = false;
|
||||
loadInitialData();
|
||||
}
|
||||
|
||||
function handleItemSuccess() {
|
||||
loadInitialData();
|
||||
}
|
||||
|
||||
function handleApplyFilters() {
|
||||
currentPage = 1;
|
||||
allItems = [];
|
||||
loadInitialData();
|
||||
}
|
||||
|
||||
function handleClearFilters() {
|
||||
filters = {
|
||||
key: '',
|
||||
description_es: ''
|
||||
};
|
||||
currentPage = 1;
|
||||
allItems = [];
|
||||
loadInitialData();
|
||||
}
|
||||
|
||||
// Crear columnas con el callback de éxito
|
||||
const tableColumns = createColumns(handleItemSuccess);
|
||||
function handleSuccess() {
|
||||
// Recargar datos
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Bultos / Embalajes - Catálogos Generales</title>
|
||||
</svelte:head>
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">packages</h1> <!-- {m["sidebar.general_catalogs.packages"]()} -->
|
||||
<p class="text-muted-foreground">
|
||||
Gestión del catálogo de bultos y embalajes
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Bulto
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4 md:p-6">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold tracking-tight">Bultos / Embalajes</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestiona los tipos de bultos y embalajes utilizados en tus operaciones
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={() => createDialogOpen = true}>
|
||||
<Plus size={16} class="mr-2" />
|
||||
Nuevo Bulto
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por clave..."
|
||||
bind:value={searchKey}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por descripción..."
|
||||
bind:value={searchDesc}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filters Card -->
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<div class="flex items-center justify-between">
|
||||
<Card.Title class="flex items-center gap-2">
|
||||
<Filter size={20} />
|
||||
Filtros
|
||||
</Card.Title>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onclick={() => showFilters = !showFilters}
|
||||
>
|
||||
{showFilters ? 'Ocultar' : 'Mostrar'}
|
||||
</Button>
|
||||
</div>
|
||||
</Card.Header>
|
||||
|
||||
{#if showFilters}
|
||||
<Card.Content>
|
||||
<form onsubmit={(e) => { e.preventDefault(); handleApplyFilters(); }} class="space-y-4">
|
||||
<div class="grid gap-4 md:grid-cols-2">
|
||||
<div class="grid gap-2">
|
||||
<Label for="filter-key">Clave</Label>
|
||||
<Input
|
||||
id="filter-key"
|
||||
bind:value={filters.key}
|
||||
placeholder="Ej: CAJA"
|
||||
/>
|
||||
</div>
|
||||
<div class="rounded-md border">
|
||||
<DataTable
|
||||
data={data.packages?.items || []}
|
||||
columns={createColumns(handleSuccess)}
|
||||
pageCount={data.packages?.pages || 0}
|
||||
totalItems={data.packages?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="filter-description">Descripción</Label>
|
||||
<Input
|
||||
id="filter-description"
|
||||
bind:value={filters.description_es}
|
||||
placeholder="Buscar en descripción..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<Button type="submit">
|
||||
<Filter size={16} class="mr-2" />
|
||||
Aplicar Filtros
|
||||
</Button>
|
||||
<Button type="button" variant="outline" onclick={handleClearFilters}>
|
||||
<Trash2 size={16} class="mr-2" />
|
||||
Limpiar
|
||||
</Button>
|
||||
<Button type="button" variant="outline" onclick={loadInitialData}>
|
||||
<RefreshCw size={16} class="mr-2" />
|
||||
Refrescar
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Card.Content>
|
||||
{/if}
|
||||
</Card.Root>
|
||||
|
||||
<!-- Error Alert -->
|
||||
{#if error}
|
||||
<Card.Root class="border-destructive">
|
||||
<Card.Content class="pt-6">
|
||||
<div class="flex items-center gap-2 text-destructive">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
<p class="font-medium">{error}</p>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
{/if}
|
||||
|
||||
<!-- Data Table Card -->
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<Card.Title>Lista de Bultos</Card.Title>
|
||||
<Card.Description>
|
||||
Total: {totalItems} bulto{totalItems !== 1 ? 's' : ''} |
|
||||
Mostrando: {allItems.length}
|
||||
</Card.Description>
|
||||
</div>
|
||||
</div>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<DataTable
|
||||
data={allItems}
|
||||
columns={tableColumns}
|
||||
{loading}
|
||||
{hasMore}
|
||||
{loadMore}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Create Dialog -->
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={handleCreateSuccess}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import { getServerApiUrl, getAuthTokens } from '$lib/server/api';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url }) => {
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
throw redirect(302, '/login');
|
||||
}
|
||||
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('page_size')) || 50;
|
||||
|
||||
const apiUrl = getServerApiUrl();
|
||||
const endpoint = `${apiUrl}api/v1/a76/ports?page=${page}&page_size=${pageSize}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Error fetching ports: ${response.status} ${response.statusText}`);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: `Error: ${response.statusText}`
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
items: data.items,
|
||||
total: data.total,
|
||||
page: data.page,
|
||||
pageSize: data.page_size,
|
||||
pages: data.pages
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching ports:', error);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: 'Failed to connect to server'
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
<script lang="ts">
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { createColumns } from '$lib/components/dashboard/ports/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/ports/create-edit-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/units_of_measure/ace/data-table.svelte'; // Reusing generic data table
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let loading = $state(false);
|
||||
|
||||
const columns = createColumns(() => {
|
||||
refreshData();
|
||||
});
|
||||
|
||||
async function refreshData() {
|
||||
loading = true;
|
||||
await invalidateAll();
|
||||
loading = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold tracking-tight">Puertos</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Catálogo de puertos
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" onclick={refreshData} disabled={loading}>
|
||||
<RefreshCw class="h-4 w-4 {loading ? 'animate-spin' : ''}" />
|
||||
</Button>
|
||||
<Button onclick={() => createDialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Content class="p-0">
|
||||
<DataTable
|
||||
data={data.items}
|
||||
{columns}
|
||||
pageCount={data.pages}
|
||||
totalItems={data.total}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={refreshData}
|
||||
/>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { getPrevalidators } from '$lib/api/dashboard/a76/prevalidators';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const response = await getPrevalidators(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
prevalidators: response.data
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,84 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
// Filtros
|
||||
let searchCode = $state($page.url.searchParams.get('code') || '');
|
||||
let searchDesc = $state($page.url.searchParams.get('description') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = [
|
||||
{ key: 'code', label: 'Código' },
|
||||
{ key: 'description', label: 'Descripción' },
|
||||
];
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
if (searchCode) url.searchParams.set('code', searchCode);
|
||||
else url.searchParams.delete('code');
|
||||
|
||||
if (searchDesc) url.searchParams.set('description', searchDesc);
|
||||
else url.searchParams.delete('description');
|
||||
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Prevalidadores</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestión del catálogo de prevalidadores
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo Prevalidador
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por código..."
|
||||
bind:value={searchCode}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por descripción..."
|
||||
bind:value={searchDesc}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<SimpleDataTable
|
||||
data={data.prevalidators?.items || []}
|
||||
columns={columns}
|
||||
pageCount={data.prevalidators?.pages || 0}
|
||||
totalItems={data.prevalidators?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
import { getSignatures } from '$lib/api/dashboard/a76/signatures';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
const name = url.searchParams.get('name');
|
||||
const position = url.searchParams.get('position');
|
||||
|
||||
if (name) filters.name = name;
|
||||
if (position) filters.position = position;
|
||||
|
||||
const response = await getSignatures(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
signatures: response.data
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,85 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
// Filtros
|
||||
let searchName = $state($page.url.searchParams.get('name') || '');
|
||||
let searchPosition = $state($page.url.searchParams.get('position') || '');
|
||||
let timeout: ReturnType<typeof setTimeout>;
|
||||
|
||||
const columns = [
|
||||
{ key: 'name', label: 'Nombre' },
|
||||
{ key: 'position', label: 'Cargo' },
|
||||
{ key: 'certificate', label: 'Certificado' },
|
||||
];
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
const url = new URL($page.url);
|
||||
if (searchName) url.searchParams.set('name', searchName);
|
||||
else url.searchParams.delete('name');
|
||||
|
||||
if (searchPosition) url.searchParams.set('position', searchPosition);
|
||||
else url.searchParams.delete('position');
|
||||
|
||||
url.searchParams.set('page', '1');
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<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>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Firma
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 items-end">
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por nombre..."
|
||||
bind:value={searchName}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
<div class="grid w-full max-w-sm items-center gap-1.5">
|
||||
<Input
|
||||
placeholder="Buscar por cargo..."
|
||||
bind:value={searchPosition}
|
||||
oninput={handleSearch}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<SimpleDataTable
|
||||
data={data.signatures?.items || []}
|
||||
columns={columns}
|
||||
pageCount={data.signatures?.pages || 0}
|
||||
totalItems={data.signatures?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
import { getUnitConversions } from '$lib/api/dashboard/a76/unit-conversions';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
|
||||
const filters: Record<string, any> = {};
|
||||
|
||||
const response = await getUnitConversions(page, pageSize, filters);
|
||||
|
||||
return {
|
||||
conversions: response.data
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
let { data } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
const columns = [
|
||||
{ key: 'from_unit_id', label: 'De Unidad' },
|
||||
{ key: 'to_unit_id', label: 'A Unidad' },
|
||||
{ key: 'conversion_factor', label: 'Factor de Conversión' },
|
||||
];
|
||||
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold tracking-tight">Conversiones de Unidades</h1>
|
||||
<p class="text-muted-foreground">
|
||||
Gestión del catálogo de conversiones de unidades de medida
|
||||
</p>
|
||||
</div>
|
||||
<Button onclick={() => dialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nueva Conversión
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<SimpleDataTable
|
||||
data={data.conversions?.items || []}
|
||||
columns={columns}
|
||||
pageCount={data.conversions?.pages || 0}
|
||||
totalItems={data.conversions?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,57 @@
|
||||
import { getServerApiUrl, getAuthTokens } from '$lib/server/api';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url }) => {
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
throw redirect(302, '/login');
|
||||
}
|
||||
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('page_size')) || 50;
|
||||
|
||||
const apiUrl = getServerApiUrl();
|
||||
// Note: apiUrl already ends with /
|
||||
const endpoint = `${apiUrl}api/v1/a76/units-of-measure/ace?page=${page}&page_size=${pageSize}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Error fetching ACE units: ${response.status} ${response.statusText}`);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: `Error: ${response.statusText}`
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
items: data.items,
|
||||
total: data.total,
|
||||
page: data.page,
|
||||
pageSize: data.page_size,
|
||||
pages: data.pages
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching ACE units:', error);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: 'Failed to connect to server'
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
<script lang="ts">
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { createColumns } from '$lib/components/dashboard/units_of_measure/ace/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/units_of_measure/ace/create-edit-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/units_of_measure/ace/data-table.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let loading = $state(false);
|
||||
|
||||
const columns = createColumns(() => {
|
||||
refreshData();
|
||||
});
|
||||
|
||||
async function refreshData() {
|
||||
loading = true;
|
||||
await invalidateAll();
|
||||
loading = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold tracking-tight">Unidades de Medida ACE</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Catálogo de unidades de medida ACE
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" onclick={refreshData} disabled={loading}>
|
||||
<RefreshCw class="h-4 w-4 {loading ? 'animate-spin' : ''}" />
|
||||
</Button>
|
||||
<Button onclick={() => createDialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Content class="p-0">
|
||||
<DataTable
|
||||
data={data.items}
|
||||
{columns}
|
||||
pageCount={data.pages}
|
||||
totalItems={data.total}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={refreshData}
|
||||
/>
|
||||
</div>
|
||||
@@ -0,0 +1,56 @@
|
||||
import { getServerApiUrl, getAuthTokens } from '$lib/server/api';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url }) => {
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
throw redirect(302, '/login');
|
||||
}
|
||||
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('page_size')) || 50;
|
||||
|
||||
const apiUrl = getServerApiUrl();
|
||||
const endpoint = `${apiUrl}api/v1/a76/units-of-measure/american?page=${page}&page_size=${pageSize}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Error fetching American units: ${response.status} ${response.statusText}`);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: `Error: ${response.statusText}`
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
items: data.items,
|
||||
total: data.total,
|
||||
page: data.page,
|
||||
pageSize: data.page_size,
|
||||
pages: data.pages
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching American units:', error);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: 'Failed to connect to server'
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
<script lang="ts">
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { createColumns } from '$lib/components/dashboard/units_of_measure/american/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/units_of_measure/american/create-edit-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/units_of_measure/ace/data-table.svelte'; // Reusing generic data table from ACE
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let loading = $state(false);
|
||||
|
||||
const columns = createColumns(() => {
|
||||
refreshData();
|
||||
});
|
||||
|
||||
async function refreshData() {
|
||||
loading = true;
|
||||
await invalidateAll();
|
||||
loading = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold tracking-tight">Unidades de Medida Americanas</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Catálogo de unidades de medida Americanas
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" onclick={refreshData} disabled={loading}>
|
||||
<RefreshCw class="h-4 w-4 {loading ? 'animate-spin' : ''}" />
|
||||
</Button>
|
||||
<Button onclick={() => createDialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Content class="p-0">
|
||||
<DataTable
|
||||
data={data.items}
|
||||
{columns}
|
||||
pageCount={data.pages}
|
||||
totalItems={data.total}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={refreshData}
|
||||
/>
|
||||
</div>
|
||||
@@ -0,0 +1,56 @@
|
||||
import { getServerApiUrl, getAuthTokens } from '$lib/server/api';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url }) => {
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
throw redirect(302, '/login');
|
||||
}
|
||||
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('page_size')) || 50;
|
||||
|
||||
const apiUrl = getServerApiUrl();
|
||||
const endpoint = `${apiUrl}api/v1/a76/units-of-measure/oma?page=${page}&page_size=${pageSize}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Error fetching OMA units: ${response.status} ${response.statusText}`);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: `Error: ${response.statusText}`
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
items: data.items,
|
||||
total: data.total,
|
||||
page: data.page,
|
||||
pageSize: data.page_size,
|
||||
pages: data.pages
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching OMA units:', error);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: 'Failed to connect to server'
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,61 @@
|
||||
<script lang="ts">
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { createColumns } from '$lib/components/dashboard/units_of_measure/oma/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/units_of_measure/oma/create-edit-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/units_of_measure/ace/data-table.svelte'; // Reusing generic data table from ACE
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let loading = $state(false);
|
||||
|
||||
const columns = createColumns(() => {
|
||||
refreshData();
|
||||
});
|
||||
|
||||
async function refreshData() {
|
||||
loading = true;
|
||||
await invalidateAll();
|
||||
loading = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-4 p-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold tracking-tight">Unidades de Medida OMA</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Catálogo de unidades de medida OMA
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" onclick={refreshData} disabled={loading}>
|
||||
<RefreshCw class="h-4 w-4 {loading ? 'animate-spin' : ''}" />
|
||||
</Button>
|
||||
<Button onclick={() => createDialogOpen = true}>
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Nuevo
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Content class="p-0">
|
||||
<DataTable
|
||||
data={data.items}
|
||||
{columns}
|
||||
pageCount={data.pages}
|
||||
totalItems={data.total}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={refreshData}
|
||||
/>
|
||||
</div>
|
||||
Reference in New Issue
Block a user