Desarrollo de rutina de CRUD para los catalogos generales
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
from typing import List, Optional, Tuple, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from fastapi import HTTPException
|
||||
import logging
|
||||
|
||||
from .models import ClassificationConcept
|
||||
from .dto import ClassificationConceptCreate, ClassificationConceptUpdate
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClassificationConceptService:
|
||||
@staticmethod
|
||||
@@ -76,6 +81,14 @@ class ClassificationConceptService:
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
try:
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error de integridad al eliminar clasificación de concepto {id}: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="No se puede eliminar esta clasificación porque tiene registros relacionados (pedimentos, facturas, etc.). Primero debe eliminar o reasignar esos registros."
|
||||
)
|
||||
|
||||
@@ -40,6 +40,55 @@ async def create_company(
|
||||
return service.create_company_manually(data, tenant_id=tenant_id)
|
||||
|
||||
|
||||
@router.get(
|
||||
"", # GET /api/v1/a76/company with pagination
|
||||
response_model=dict,
|
||||
summary="Get companies with pagination",
|
||||
)
|
||||
async def list_companies(
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
name: Optional[str] = None,
|
||||
rfc: Optional[str] = None,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Get paginated list of companies for current tenant with optional filters"""
|
||||
tenant_id = current_user.get("tenant_id")
|
||||
if not tenant_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Tenant ID not found in user data",
|
||||
)
|
||||
|
||||
skip = (page - 1) * page_size
|
||||
filters = {}
|
||||
if name:
|
||||
filters["name"] = name
|
||||
if rfc:
|
||||
filters["rfc"] = rfc
|
||||
|
||||
service = CompanyService(db)
|
||||
items, total = service.get_all(
|
||||
db,
|
||||
tenant_id,
|
||||
company_id=0, # Not used for companies
|
||||
skip=skip,
|
||||
limit=page_size,
|
||||
filters=filters if filters else None
|
||||
)
|
||||
|
||||
total_pages = (total + page_size - 1) // page_size
|
||||
|
||||
return {
|
||||
"items": [CompanyResponseDTO.model_validate(item) for item in items],
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"pages": total_pages,
|
||||
}
|
||||
|
||||
|
||||
@router.get(
|
||||
"/my-companies",
|
||||
response_model=List[CompanyResponseDTO],
|
||||
@@ -189,16 +238,89 @@ async def get_program_info(
|
||||
"prosec_authorization": company.prosec_authorization,
|
||||
}
|
||||
|
||||
base_router = TenantCRUDRoutes(
|
||||
service=CompanyService,
|
||||
create_schema=None,
|
||||
update_schema=CompanyUpdateDTO,
|
||||
response_schema=CompanyResponseDTO,
|
||||
prefix="",
|
||||
tags=[],
|
||||
id_name="id",
|
||||
enable_list=True,
|
||||
enable_filters=True,
|
||||
).router
|
||||
|
||||
router.include_router(base_router)
|
||||
@router.get(
|
||||
"/{company_id}",
|
||||
response_model=CompanyResponseDTO,
|
||||
summary="Get company by ID",
|
||||
)
|
||||
async def get_company(
|
||||
company_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Get a specific company by ID"""
|
||||
tenant_id = current_user.get("tenant_id")
|
||||
if not tenant_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Tenant ID not found in user data",
|
||||
)
|
||||
|
||||
company = CompanyService.get_by_id(db, company_id, tenant_id, 0)
|
||||
if not company:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Company not found",
|
||||
)
|
||||
|
||||
return CompanyResponseDTO.model_validate(company)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{company_id}",
|
||||
response_model=CompanyResponseDTO,
|
||||
summary="Update company",
|
||||
)
|
||||
async def update_company(
|
||||
company_id: int,
|
||||
data: CompanyUpdateDTO,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Update a company"""
|
||||
tenant_id = current_user.get("tenant_id")
|
||||
if not tenant_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Tenant ID not found in user data",
|
||||
)
|
||||
|
||||
updated_company = CompanyService.update(
|
||||
db, company_id, tenant_id, 0, data
|
||||
)
|
||||
if not updated_company:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Company not found",
|
||||
)
|
||||
|
||||
return CompanyResponseDTO.model_validate(updated_company)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{company_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
summary="Delete company",
|
||||
)
|
||||
async def delete_company(
|
||||
company_id: int,
|
||||
db: Session = Depends(get_core_db),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
"""Delete a company"""
|
||||
tenant_id = current_user.get("tenant_id")
|
||||
if not tenant_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Tenant ID not found in user data",
|
||||
)
|
||||
|
||||
success = CompanyService.delete(db, company_id, tenant_id, 0)
|
||||
if not success:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Company not found",
|
||||
)
|
||||
|
||||
return None
|
||||
@@ -137,10 +137,20 @@ class CompanyService:
|
||||
db.delete(company)
|
||||
db.commit()
|
||||
return True
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"IntegrityError deleting company {company_id}: {str(e)}")
|
||||
# Check if it's a foreign key constraint
|
||||
if "foreign key constraint" in str(e).lower():
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="No se puede eliminar la empresa porque tiene registros relacionados (facturas, conceptos, etc.)"
|
||||
)
|
||||
raise HTTPException(status_code=400, detail="Error al eliminar la empresa")
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error deleting company {company_id}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Error deleting company")
|
||||
raise HTTPException(status_code=500, detail="Error al eliminar la empresa")
|
||||
|
||||
# Custom methods
|
||||
def get_companies_by_tenant(self, tenant_id: int) -> List[Company]:
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
from typing import List, Optional, Tuple, Dict, Any
|
||||
import logging
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from fastapi import HTTPException
|
||||
|
||||
from .models import Concept
|
||||
from .dto import ConceptCreate, ConceptUpdate
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConceptService:
|
||||
@staticmethod
|
||||
@@ -74,6 +80,20 @@ class ConceptService:
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
try:
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"IntegrityError deleting concept {id}: {str(e)}")
|
||||
if "foreign key constraint" in str(e).lower():
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="No se puede eliminar el concepto porque tiene registros relacionados"
|
||||
)
|
||||
raise HTTPException(status_code=400, detail="Error al eliminar el concepto")
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error deleting concept {id}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Error al eliminar el concepto")
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
from typing import List, Optional, Tuple, Dict, Any
|
||||
import logging
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from fastapi import HTTPException
|
||||
|
||||
from .models import CustomsBrokerConcept
|
||||
from .dto import CustomsBrokerConceptCreate, CustomsBrokerConceptUpdate
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CustomsBrokerConceptService:
|
||||
@staticmethod
|
||||
@@ -76,6 +82,20 @@ class CustomsBrokerConceptService:
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
try:
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"IntegrityError deleting customs broker concept {id}: {str(e)}")
|
||||
if "foreign key constraint" in str(e).lower():
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="No se puede eliminar el concepto porque tiene registros relacionados"
|
||||
)
|
||||
raise HTTPException(status_code=400, detail="Error al eliminar el concepto")
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error deleting customs broker concept {id}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Error al eliminar el concepto")
|
||||
|
||||
@@ -131,14 +131,21 @@ class DodaService:
|
||||
db: Session, id: int, tenant_id: int, company_id: int
|
||||
) -> bool:
|
||||
"""Delete a DODA"""
|
||||
try:
|
||||
db_doda = DodaService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_doda:
|
||||
return False
|
||||
db_doda = DodaService.get_by_id(db, id, tenant_id, company_id)
|
||||
if not db_doda:
|
||||
return False
|
||||
|
||||
try:
|
||||
db.delete(db_doda)
|
||||
db.commit()
|
||||
return True
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error de integridad al eliminar DODA {id}: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="No se puede eliminar este DODA porque tiene registros relacionados. Primero debe eliminar o reasignar esos registros."
|
||||
)
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error deleting DODA: {str(e)}")
|
||||
|
||||
@@ -4,17 +4,13 @@ from .models import Legend
|
||||
from .dto import LegendCreate, LegendResponse, LegendUpdate
|
||||
from .service import LegendService
|
||||
|
||||
router = APIRouter(prefix="/legends", tags=["a76.general_catalogs.legends"])
|
||||
|
||||
legend_crud = TenantCRUDRoutes(
|
||||
router = TenantCRUDRoutes(
|
||||
service=LegendService,
|
||||
create_schema=LegendCreate,
|
||||
update_schema=LegendUpdate,
|
||||
response_schema=LegendResponse,
|
||||
prefix="/legends",
|
||||
tags=["Legends"],
|
||||
tags=["a76.general_catalogs.legends"],
|
||||
resource_name="Legend",
|
||||
enable_list=True,
|
||||
)
|
||||
|
||||
router.include_router(legend_crud.router)
|
||||
).router
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
from typing import List, Optional, Tuple, Dict, Any
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from fastapi import HTTPException
|
||||
import logging
|
||||
|
||||
from .models import Legend
|
||||
from .dto import LegendCreate, LegendUpdate
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LegendService:
|
||||
@staticmethod
|
||||
@@ -63,8 +68,8 @@ class LegendService:
|
||||
def update(
|
||||
db: Session,
|
||||
id: int,
|
||||
data: LegendUpdate,
|
||||
tenant_id: int,
|
||||
data: LegendUpdate,
|
||||
company_id: int
|
||||
) -> Optional[Legend]:
|
||||
db_obj = LegendService.get_by_id(db, id, tenant_id, company_id)
|
||||
@@ -90,6 +95,14 @@ class LegendService:
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
try:
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error de integridad al eliminar leyenda {id}: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="No se puede eliminar esta leyenda porque tiene registros relacionados (pedimentos, facturas, etc.). Primero debe eliminar o reasignar esos registros."
|
||||
)
|
||||
|
||||
@@ -3,11 +3,16 @@ Service layer for Packages (GBultos).
|
||||
"""
|
||||
|
||||
from typing import Optional, Tuple, List, Dict, Any
|
||||
import logging
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from fastapi import HTTPException
|
||||
|
||||
from . import dto, models
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PackageService:
|
||||
"""Service for Package CRUD operations with tenant support"""
|
||||
@@ -109,6 +114,20 @@ class PackageService:
|
||||
if not package:
|
||||
return False
|
||||
|
||||
db.delete(package)
|
||||
db.commit()
|
||||
return True
|
||||
try:
|
||||
db.delete(package)
|
||||
db.commit()
|
||||
return True
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"IntegrityError deleting package {package_id}: {str(e)}")
|
||||
if "foreign key constraint" in str(e).lower():
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="No se puede eliminar el bulto porque tiene registros relacionados"
|
||||
)
|
||||
raise HTTPException(status_code=400, detail="Error al eliminar el bulto")
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error deleting package {package_id}: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Error al eliminar el bulto")
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
from typing import List, Optional, Tuple, Dict, Any, Type
|
||||
from sqlalchemy import Sequence
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from fastapi import HTTPException
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from .models import (
|
||||
UnitOfMeasureACE, UnitOfMeasureOMA, UnitOfMeasureAmerican, UnitOfMeasureCustoms,
|
||||
@@ -102,9 +107,17 @@ class BaseService:
|
||||
if not db_obj:
|
||||
return False
|
||||
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
try:
|
||||
db.delete(db_obj)
|
||||
db.commit()
|
||||
return True
|
||||
except IntegrityError as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error de integridad al eliminar {cls.model.__name__} {id}: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="No se puede eliminar esta unidad de medida porque tiene registros relacionados. Primero debe eliminar o reasignar esos registros."
|
||||
)
|
||||
|
||||
|
||||
class UnitOfMeasureACEService(BaseService):
|
||||
|
||||
@@ -41,11 +41,11 @@
|
||||
"valuation_methods": "Valuation Methods",
|
||||
"countries": "Countries",
|
||||
"ports": "Ports",
|
||||
"unit_measures": "Unit Measures",
|
||||
"um_customs_mex": "U.M. Customs Mexico",
|
||||
"um_customs_ame": "U.M. Customs America",
|
||||
"um_ace": "U.M. ACE",
|
||||
"um_oma": "U.M. OMA",
|
||||
"unit_measures": "Units of Measure - General",
|
||||
"um_customs_mex": "Units of Measure - Mexican Customs",
|
||||
"um_customs_ame": "Units of Measure - American Customs",
|
||||
"um_ace": "Units of Measure - ACE",
|
||||
"um_oma": "Units of Measure - OMA",
|
||||
"conversions": "Conversions",
|
||||
"equivalences": "Equivalences",
|
||||
"exchange_rates": "Exchange Rates",
|
||||
|
||||
@@ -41,11 +41,11 @@
|
||||
"valuation_methods": "Metódos de valoración",
|
||||
"countries": "Países",
|
||||
"ports": "Puertos",
|
||||
"unit_measures": "Unidades de medida",
|
||||
"um_customs_mex": "U.M. Aduanas Mexicanas",
|
||||
"um_customs_ame": "U.M. Aduanas Americanas",
|
||||
"um_ace": "U.M. ACE",
|
||||
"um_oma": "U.M. OMA",
|
||||
"unit_measures": "Unidades de medida general",
|
||||
"um_customs_mex": "Unidades de medida - Aduanas Mexicanas",
|
||||
"um_customs_ame": "Unidades de medida - Aduanas Americanas",
|
||||
"um_ace": "Unidades de medida - ACE",
|
||||
"um_oma": "Unidades de medida - OMA",
|
||||
"conversions": "Conversiones",
|
||||
"equivalences": "Equivalencias",
|
||||
"exchange_rates": "Tipos de cambio",
|
||||
|
||||
@@ -194,6 +194,14 @@ async function fetchApi<T = any>(
|
||||
}
|
||||
}
|
||||
|
||||
// Manejar respuestas sin contenido (204 No Content)
|
||||
if (response.status === 204) {
|
||||
return {
|
||||
data: null as T,
|
||||
status: response.status
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -37,36 +37,28 @@ export async function getClassificationConcepts(
|
||||
...filters
|
||||
});
|
||||
|
||||
|
||||
const response = await api.get(`/v1/a76/classification-concepts/?${params.toString()}`);
|
||||
return response.data;
|
||||
return await api.get(`/v1/a76/classification-concepts/?${params.toString()}`);
|
||||
}
|
||||
|
||||
export async function getClassificationConcept(id: number, companyId: number): Promise<ClassificationConcept> {
|
||||
const response = await api.get(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`);
|
||||
return response.data;
|
||||
export async function getClassificationConcept(id: number, companyId: number): Promise<ApiResponse<ClassificationConcept>> {
|
||||
return await api.get(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
export async function createClassificationConcept(
|
||||
data: ClassificationConceptCreate,
|
||||
companyId: number
|
||||
): Promise<ClassificationConcept> {
|
||||
// 👇 AQUÍ ESTABA EL ERROR GRAVE
|
||||
const response = await api.post(`/v1/a76/classification-concepts/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
): Promise<ApiResponse<ClassificationConcept>> {
|
||||
return await api.post(`/v1/a76/classification-concepts/?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
|
||||
export async function updateClassificationConcept(
|
||||
id: number,
|
||||
data: ClassificationConceptUpdate,
|
||||
companyId: number // <-- Faltaba esto
|
||||
): Promise<ClassificationConcept> {
|
||||
const response = await api.put(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
companyId: number
|
||||
): Promise<ApiResponse<ClassificationConcept>> {
|
||||
return await api.put(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
|
||||
export async function deleteClassificationConcept(id: number, companyId: number): Promise<void> {
|
||||
await api.delete(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`);
|
||||
export async function deleteClassificationConcept(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/classification-concepts/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
@@ -1,91 +1,115 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
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 Company {
|
||||
id: number;
|
||||
tenant_id: number;
|
||||
name: string | null;
|
||||
rfc: string | null;
|
||||
curp?: 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;
|
||||
responsible_rfc?: string | null;
|
||||
position?: string | null;
|
||||
has_express_line?: boolean;
|
||||
is_service_company?: boolean;
|
||||
order_format_type?: string | null;
|
||||
ctpat_svi?: string | null;
|
||||
trusted_exporter_number?: 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 CompanyCreate {
|
||||
name?: string | null;
|
||||
rfc?: string | null;
|
||||
curp?: 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;
|
||||
responsible_rfc?: string | null;
|
||||
position?: string | null;
|
||||
has_express_line?: boolean;
|
||||
is_service_company?: boolean;
|
||||
order_format_type?: string | null;
|
||||
ctpat_svi?: string | null;
|
||||
trusted_exporter_number?: 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 CompanyUpdate {
|
||||
name?: string | null;
|
||||
rfc?: string | null;
|
||||
curp?: 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;
|
||||
responsible_rfc?: string | null;
|
||||
position?: string | null;
|
||||
has_express_line?: boolean;
|
||||
is_service_company?: boolean;
|
||||
order_format_type?: string | null;
|
||||
ctpat_svi?: string | null;
|
||||
trusted_exporter_number?: string | null;
|
||||
}
|
||||
|
||||
export interface CompanyListResponse {
|
||||
items: Company[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
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 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(`/v1/a76/company?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
export async function getCompany(id: number): Promise<ApiResponse<Company>> {
|
||||
return await api.get(`/a76/company/${id}`);
|
||||
}
|
||||
export async function getCompany(id: number): Promise<ApiResponse<Company>> {
|
||||
return await api.get(`/v1/a76/company/${id}`);
|
||||
}
|
||||
|
||||
export async function createCompany(data: CompanyCreate): Promise<ApiResponse<Company>> {
|
||||
return await api.post(`/v1/a76/company`, data);
|
||||
}
|
||||
export async function createCompany(data: CompanyCreate): Promise<ApiResponse<Company>> {
|
||||
return await api.post(`/v1/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 updateCompany(id: number, data: CompanyUpdate): Promise<ApiResponse<Company>> {
|
||||
return await api.put(`/v1/a76/company/${id}`, data);
|
||||
}
|
||||
|
||||
export async function deleteCompany(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/company/${id}`);
|
||||
}
|
||||
export async function deleteCompany(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/company/${id}`);
|
||||
}
|
||||
|
||||
@@ -57,33 +57,25 @@ export async function getConcepts(
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/v1/a76/concepts/?${params.toString()}`);
|
||||
return response.data;
|
||||
return await api.get(`/v1/a76/concepts?${params.toString()}`);
|
||||
}
|
||||
|
||||
|
||||
export async function getConcept(id: number, companyId: number): Promise<Concept> {
|
||||
|
||||
const response = await api.get(`/v1/a76/concepts/${id}/?company_id=${companyId}`);
|
||||
return response.data;
|
||||
export async function getConcept(id: number, companyId: number): Promise<ApiResponse<Concept>> {
|
||||
return await api.get(`/v1/a76/concepts/${id}?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
|
||||
export async function createConcept(data: ConceptCreate, companyId: number): Promise<Concept> {
|
||||
|
||||
const response = await api.post(`/v1/a76/concepts/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
export async function createConcept(data: ConceptCreate, companyId: number): Promise<ApiResponse<Concept>> {
|
||||
return await api.post(`/v1/a76/concepts?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
|
||||
export async function updateConcept(id: number, data: ConceptUpdate, companyId: number): Promise<Concept> {
|
||||
|
||||
const response = await api.put(`/v1/a76/concepts/${id}/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
export async function updateConcept(id: number, data: ConceptUpdate, companyId: number): Promise<ApiResponse<Concept>> {
|
||||
return await api.put(`/v1/a76/concepts/${id}?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
|
||||
export async function deleteConcept(id: number, companyId: number): Promise<void> {
|
||||
|
||||
await api.delete(`/v1/a76/concepts/${id}/?company_id=${companyId}`);
|
||||
export async function deleteConcept(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/concepts/${id}?company_id=${companyId}`);
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
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,
|
||||
companyId: number,
|
||||
filters: Record<string, any> = {},
|
||||
): Promise<ApiResponse<CustomsBrokerConceptListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
return await api.get(`/v1/a76/customs-broker-concepts?${params.toString()}`);
|
||||
}
|
||||
|
||||
export async function getCustomsBrokerConcept(id: number, companyId: number): Promise<ApiResponse<CustomsBrokerConcept>> {
|
||||
return await api.get(`/v1/a76/customs-broker-concepts/${id}?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
export async function createCustomsBrokerConcept(data: CustomsBrokerConceptCreate, companyId: number): Promise<ApiResponse<CustomsBrokerConcept>> {
|
||||
return await api.post(`/v1/a76/customs-broker-concepts?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function updateCustomsBrokerConcept(id: number, data: CustomsBrokerConceptUpdate, companyId: number): Promise<ApiResponse<CustomsBrokerConcept>> {
|
||||
return await api.put(`/v1/a76/customs-broker-concepts/${id}?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function deleteCustomsBrokerConcept(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/customs-broker-concepts/${id}?company_id=${companyId}`);
|
||||
}
|
||||
@@ -38,35 +38,28 @@ export async function getLegends(
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
const response = await api.get(`/v1/a76/legends/?${params.toString()}`);
|
||||
return response.data;
|
||||
return await api.get(`/v1/a76/legends/?${params.toString()}`);
|
||||
}
|
||||
|
||||
export async function getLegend(id: number, companyId: number): Promise<Legend> {
|
||||
const response = await api.get(`/v1/a76/legends/${id}/?company_id=${companyId}`);
|
||||
return response.data;
|
||||
export async function getLegend(id: number, companyId: number): Promise<ApiResponse<Legend>> {
|
||||
return await api.get(`/v1/a76/legends/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
|
||||
export async function createLegend(
|
||||
data: LegendCreate,
|
||||
companyId: number
|
||||
): Promise<Legend> {
|
||||
|
||||
const response = await api.post(`/v1/a76/legends/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
): Promise<ApiResponse<Legend>> {
|
||||
return await api.post(`/v1/a76/legends/?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function updateLegend(
|
||||
id: number,
|
||||
data: LegendUpdate,
|
||||
companyId: number
|
||||
): Promise<Legend> {
|
||||
|
||||
const response = await api.put(`/v1/a76/legends/${id}/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
): Promise<ApiResponse<Legend>> {
|
||||
return await api.put(`/v1/a76/legends/${id}/?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function deleteLegend(id: number, companyId: number): Promise<void> {
|
||||
await api.delete(`/v1/a76/legends/${id}/?company_id=${companyId}`);
|
||||
export async function deleteLegend(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/legends/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
@@ -1,79 +1,78 @@
|
||||
import { api } from '$lib/api';
|
||||
import type { ApiResponse } from '$lib/api';
|
||||
import type { PaginatedResponse } from '$lib/types';
|
||||
|
||||
export interface MultiCurrencyType {
|
||||
id: number;
|
||||
|
||||
currency_type_code: string;
|
||||
country_key: string | null;
|
||||
conversion_factor: number | null;
|
||||
publication_date: number;
|
||||
tenant_id: number;
|
||||
company_id: number;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
id: number;
|
||||
currency_type_code: string;
|
||||
country_key: string | null;
|
||||
conversion_factor: number | null;
|
||||
publication_date: number;
|
||||
company_id: number;
|
||||
tenant_id: number;
|
||||
}
|
||||
|
||||
export interface MultiCurrencyTypeCreate {
|
||||
currency_type_code: string;
|
||||
country_key?: string | null;
|
||||
conversion_factor?: number | null;
|
||||
publication_date: number;
|
||||
currency_type_code: string;
|
||||
country_key?: string | null;
|
||||
conversion_factor?: number | null;
|
||||
publication_date: number;
|
||||
}
|
||||
|
||||
export interface MultiCurrencyTypeUpdate extends Partial<MultiCurrencyTypeCreate> {}
|
||||
export interface MultiCurrencyTypeUpdate {
|
||||
currency_type_code?: string;
|
||||
country_key?: string | null;
|
||||
conversion_factor?: number | null;
|
||||
publication_date?: number;
|
||||
}
|
||||
|
||||
export interface MultiCurrencyTypeListResponse {
|
||||
items: MultiCurrencyType[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
export interface MultiCurrencyTypeListResponse extends PaginatedResponse {
|
||||
items: MultiCurrencyType[];
|
||||
}
|
||||
|
||||
export async function getMultiCurrencyTypes(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
companyId: number,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<MultiCurrencyTypeListResponse>> {
|
||||
const params = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
companyId: number,
|
||||
page?: number,
|
||||
pageSize?: number
|
||||
): Promise<MultiCurrencyTypeListResponse> {
|
||||
const params = new URLSearchParams({ company_id: companyId.toString() });
|
||||
if (page) params.append('page', page.toString());
|
||||
if (pageSize) params.append('page_size', pageSize.toString());
|
||||
|
||||
// Agregamos /v1 y el path correcto
|
||||
const response = await api.get(`/v1/a76/multi_currency_types/?${params.toString()}`);
|
||||
return response.data;
|
||||
return api.get<MultiCurrencyTypeListResponse>(`/v1/a76/multi-currency-types/?${params.toString()}`);
|
||||
}
|
||||
|
||||
|
||||
export async function getMultiCurrencyType(id: number, companyId: number): Promise<MultiCurrencyType> {
|
||||
const response = await api.get(`/v1/a76/multi_currency_types/${id}/?company_id=${companyId}`);
|
||||
return response.data;
|
||||
export async function getMultiCurrencyType(
|
||||
multiCurrencyTypeId: number,
|
||||
companyId: number
|
||||
): Promise<MultiCurrencyType> {
|
||||
const params = new URLSearchParams({ company_id: companyId.toString() });
|
||||
return api.get<MultiCurrencyType>(`/v1/a76/multi-currency-types/${multiCurrencyTypeId}?${params.toString()}`);
|
||||
}
|
||||
|
||||
export async function createMultiCurrencyType(
|
||||
data: MultiCurrencyTypeCreate,
|
||||
companyId: number
|
||||
data: MultiCurrencyTypeCreate,
|
||||
companyId: number
|
||||
): Promise<MultiCurrencyType> {
|
||||
const response = await api.post(`/v1/a76/multi_currency_types/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
const params = new URLSearchParams({ company_id: companyId.toString() });
|
||||
return api.post<MultiCurrencyType>(`/v1/a76/multi-currency-types/?${params.toString()}`, data);
|
||||
}
|
||||
|
||||
|
||||
export async function updateMultiCurrencyType(
|
||||
id: number,
|
||||
data: MultiCurrencyTypeUpdate,
|
||||
companyId: number
|
||||
multiCurrencyTypeId: number,
|
||||
data: MultiCurrencyTypeUpdate,
|
||||
companyId: number
|
||||
): Promise<MultiCurrencyType> {
|
||||
const response = await api.put(`/v1/a76/multi_currency_types/${id}/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
const params = new URLSearchParams({ company_id: companyId.toString() });
|
||||
return api.put<MultiCurrencyType>(
|
||||
`/v1/a76/multi-currency-types/${multiCurrencyTypeId}?${params.toString()}`,
|
||||
data
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export async function deleteMultiCurrencyType(id: number, companyId: number): Promise<void> {
|
||||
await api.delete(`/v1/a76/multi_currency_types/${id}/?company_id=${companyId}`);
|
||||
export async function deleteMultiCurrencyType(
|
||||
multiCurrencyTypeId: number,
|
||||
companyId: number
|
||||
): Promise<void> {
|
||||
const params = new URLSearchParams({ company_id: companyId.toString() });
|
||||
return api.delete(`/v1/a76/multi-currency-types/${multiCurrencyTypeId}?${params.toString()}`);
|
||||
}
|
||||
@@ -52,22 +52,19 @@ export async function getPackages(
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await api.get(`/v1/a76/packages/?${params.toString()}`);
|
||||
return response.data;
|
||||
return await api.get(`/v1/a76/packages?${params.toString()}`);
|
||||
}
|
||||
|
||||
|
||||
export async function getPackage(id: number, companyId: number): Promise<Package> {
|
||||
const response = await api.get(`/v1/a76/packages/${id}/?company_id=${companyId}`);
|
||||
return response.data;
|
||||
export async function getPackage(id: number, companyId: number): Promise<ApiResponse<Package>> {
|
||||
return await api.get(`/v1/a76/packages/${id}?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
export async function createPackage(
|
||||
data: PackageCreate,
|
||||
companyId: number
|
||||
): Promise<Package> {
|
||||
const response = await api.post(`/v1/a76/packages/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
): Promise<ApiResponse<Package>> {
|
||||
return await api.post(`/v1/a76/packages?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
|
||||
@@ -75,11 +72,10 @@ export async function updatePackage(
|
||||
id: number,
|
||||
data: PackageUpdate,
|
||||
companyId: number
|
||||
): Promise<Package> {
|
||||
const response = await api.put(`/v1/a76/packages/${id}/?company_id=${companyId}`, data);
|
||||
return response.data;
|
||||
): Promise<ApiResponse<Package>> {
|
||||
return await api.put(`/v1/a76/packages/${id}?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function deletePackage(id: number, companyId: number): Promise<void> {
|
||||
await api.delete(`/v1/a76/packages/${id}/?company_id=${companyId}`);
|
||||
export async function deletePackage(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/packages/${id}?company_id=${companyId}`);
|
||||
}
|
||||
@@ -73,5 +73,5 @@ export async function updateUnitConversion(
|
||||
}
|
||||
|
||||
export async function deleteUnitConversion(id: number, companyId: number): Promise<void> {
|
||||
await api.delete(`/v1/a76/unit_conversions/${id}/?company_id=${companyId}`);
|
||||
await api.delete(`/v1/a76/unit-conversions/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
@@ -31,26 +31,28 @@ export interface UnitOfMeasureACEListResponse {
|
||||
export async function getUnitsOfMeasureACE(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
companyId: number,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<UnitOfMeasureACEListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/units-of-measure/ace?${queryParams.toString()}`);
|
||||
return await api.get(`/v1/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 createUnitOfMeasureACE(data: UnitOfMeasureACECreate, companyId: number): Promise<ApiResponse<UnitOfMeasureACE>> {
|
||||
return await api.post(`/v1/a76/units-of-measure/ace/?company_id=${companyId}`, 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 updateUnitOfMeasureACE(id: number, data: UnitOfMeasureACEUpdate, companyId: number): Promise<ApiResponse<UnitOfMeasureACE>> {
|
||||
return await api.put(`/v1/a76/units-of-measure/ace/${id}/?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function deleteUnitOfMeasureACE(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/units-of-measure/ace/${id}`);
|
||||
export async function deleteUnitOfMeasureACE(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/units-of-measure/ace/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
// --- OMA ---
|
||||
@@ -81,28 +83,30 @@ export interface UnitOfMeasureOMAListResponse {
|
||||
}
|
||||
|
||||
export async function getUnitsOfMeasureOMA(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
companyId: number,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<UnitOfMeasureOMAListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/units-of-measure/oma?${queryParams.toString()}`);
|
||||
return await api.get(`/v1/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 createUnitOfMeasureOMA(data: UnitOfMeasureOMACreate, companyId: number): Promise<ApiResponse<UnitOfMeasureOMA>> {
|
||||
return await api.post(`/v1/a76/units-of-measure/oma/?company_id=${companyId}`, 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 updateUnitOfMeasureOMA(id: number, data: UnitOfMeasureOMAUpdate, companyId: number): Promise<ApiResponse<UnitOfMeasureOMA>> {
|
||||
return await api.put(`/v1/a76/units-of-measure/oma/${id}/?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function deleteUnitOfMeasureOMA(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/units-of-measure/oma/${id}`);
|
||||
export async function deleteUnitOfMeasureOMA(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/units-of-measure/oma/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
// --- American ---
|
||||
@@ -133,26 +137,139 @@ export interface UnitOfMeasureAmericanListResponse {
|
||||
}
|
||||
|
||||
export async function getUnitsOfMeasureAmerican(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
companyId: number,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<UnitOfMeasureAmericanListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/a76/units-of-measure/american?${queryParams.toString()}`);
|
||||
return await api.get(`/v1/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 createUnitOfMeasureAmerican(data: UnitOfMeasureAmericanCreate, companyId: number): Promise<ApiResponse<UnitOfMeasureAmerican>> {
|
||||
return await api.post(`/v1/a76/units-of-measure/american/?company_id=${companyId}`, 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 updateUnitOfMeasureAmerican(id: number, data: UnitOfMeasureAmericanUpdate, companyId: number): Promise<ApiResponse<UnitOfMeasureAmerican>> {
|
||||
return await api.put(`/v1/a76/units-of-measure/american/${id}/?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function deleteUnitOfMeasureAmerican(id: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/a76/units-of-measure/american/${id}`);
|
||||
export async function deleteUnitOfMeasureAmerican(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/units-of-measure/american/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
// --- General ---
|
||||
export interface UnitOfMeasureGeneral {
|
||||
id: number;
|
||||
code: string;
|
||||
description: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureGeneralCreate {
|
||||
code: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureGeneralUpdate {
|
||||
code?: string;
|
||||
description?: string | null;
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureGeneralListResponse {
|
||||
items: UnitOfMeasureGeneral[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getUnitsOfMeasureGeneral(
|
||||
page = 1,
|
||||
pageSize = 50,
|
||||
companyId: number,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<UnitOfMeasureGeneralListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/v1/a76/units-of-measure/general/?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
export async function createUnitOfMeasureGeneral(data: UnitOfMeasureGeneralCreate, companyId: number): Promise<ApiResponse<UnitOfMeasureGeneral>> {
|
||||
return await api.post(`/v1/a76/units-of-measure/general/?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function updateUnitOfMeasureGeneral(id: number, data: UnitOfMeasureGeneralUpdate, companyId: number): Promise<ApiResponse<UnitOfMeasureGeneral>> {
|
||||
return await api.put(`/v1/a76/units-of-measure/general/${id}/?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function deleteUnitOfMeasureGeneral(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/units-of-measure/general/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
// --- Customs ---
|
||||
export interface UnitOfMeasureCustoms {
|
||||
id: number;
|
||||
code: string;
|
||||
description: string | null;
|
||||
scaii_unit_code: string | null;
|
||||
created_at: string | null;
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureCustomsCreate {
|
||||
code: string;
|
||||
description?: string | null;
|
||||
scaii_unit_code?: string | null;
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureCustomsUpdate {
|
||||
code?: string;
|
||||
description?: string | null;
|
||||
scaii_unit_code?: string | null;
|
||||
}
|
||||
|
||||
export interface UnitOfMeasureCustomsListResponse {
|
||||
items: UnitOfMeasureCustoms[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
export async function getUnitsOfMeasureCustoms(
|
||||
page: number = 1,
|
||||
pageSize: number = 50,
|
||||
companyId: number,
|
||||
filters: Record<string, any> = {}
|
||||
): Promise<ApiResponse<UnitOfMeasureCustomsListResponse>> {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
return await api.get(`/v1/a76/units-of-measure/customs/?${queryParams.toString()}`);
|
||||
}
|
||||
|
||||
export async function createUnitOfMeasureCustoms(data: UnitOfMeasureCustomsCreate, companyId: number): Promise<ApiResponse<UnitOfMeasureCustoms>> {
|
||||
return await api.post(`/v1/a76/units-of-measure/customs/?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function updateUnitOfMeasureCustoms(id: number, data: UnitOfMeasureCustomsUpdate, companyId: number): Promise<ApiResponse<UnitOfMeasureCustoms>> {
|
||||
return await api.put(`/v1/a76/units-of-measure/customs/${id}/?company_id=${companyId}`, data);
|
||||
}
|
||||
|
||||
export async function deleteUnitOfMeasureCustoms(id: number, companyId: number): Promise<ApiResponse<void>> {
|
||||
return await api.delete(`/v1/a76/units-of-measure/customs/${id}/?company_id=${companyId}`);
|
||||
}
|
||||
|
||||
@@ -5,44 +5,44 @@ import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<ExchangeRate>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'date',
|
||||
header: 'Fecha',
|
||||
cell: ({ row }) => {
|
||||
const dateStr = row.original.date;
|
||||
if (!dateStr) return 'N/A';
|
||||
const date = new Date(dateStr);
|
||||
return date.toLocaleDateString('es-MX');
|
||||
{
|
||||
accessorKey: 'date',
|
||||
header: 'Fecha',
|
||||
cell: ({ row }) => {
|
||||
const dateStr = row.original.date;
|
||||
if (!dateStr) return 'N/A';
|
||||
const date = new Date(dateStr);
|
||||
return date.toLocaleDateString('es-MX');
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'value',
|
||||
header: 'Valor',
|
||||
cell: ({ row }) => {
|
||||
const value = row.original.value;
|
||||
if (value === null || value === undefined) return 'N/A';
|
||||
return value.toFixed(6);
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'local_currency',
|
||||
header: 'Moneda Local',
|
||||
cell: ({ row }) => row.original.local_currency ?? 'N/A'
|
||||
},
|
||||
{
|
||||
accessorKey: 'foreign_currency',
|
||||
header: 'Moneda Extranjera',
|
||||
cell: ({ row }) => row.original.foreign_currency ?? 'N/A'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'value',
|
||||
header: 'Tipo de Cambio',
|
||||
cell: ({ row }) => {
|
||||
const value = row.original.value;
|
||||
if (value === null || value === undefined) return 'N/A';
|
||||
return value.toFixed(6);
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: 'local_currency',
|
||||
header: 'Moneda Local',
|
||||
cell: ({ row }) => row.original.local_currency ?? 'N/A'
|
||||
},
|
||||
{
|
||||
accessorKey: 'foreign_currency',
|
||||
header: 'Moneda Extranjera',
|
||||
cell: ({ row }) => row.original.foreign_currency ?? 'N/A'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -75,8 +75,10 @@
|
||||
|
||||
if (isEdit && item) {
|
||||
await updateExchangeRate(item.id, dataToSend, companyId);
|
||||
alert(`✅ Tipo de cambio actualizado correctamente`);
|
||||
} else {
|
||||
await createExchangeRate(dataToSend, companyId);
|
||||
alert(`✅ Tipo de cambio creado correctamente`);
|
||||
}
|
||||
|
||||
open = false;
|
||||
|
||||
@@ -27,23 +27,21 @@
|
||||
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) {
|
||||
alert('No hay compañía seleccionada');
|
||||
alert('❌ Error: No hay compañía seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
await deleteExchangeRate(item.id, companyId);
|
||||
|
||||
// Éxito
|
||||
alert(`✅ Tipo de cambio del ${new Date(item.date).toLocaleDateString('es-MX')} eliminado correctamente`);
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
} catch (err: any) {
|
||||
error = err.message || 'Error al eliminar el tipo de cambio';
|
||||
alert(`Error: ${error}`);
|
||||
alert(`❌ Error: ${error}`);
|
||||
console.error('Error deleting:', err);
|
||||
} finally {
|
||||
loading = false;
|
||||
|
||||
@@ -1,123 +1,106 @@
|
||||
<script lang="ts" generics="TData, TValue">
|
||||
import { onMount } from 'svelte';
|
||||
import {
|
||||
type ColumnDef,
|
||||
getCoreRowModel
|
||||
} from "@tanstack/table-core";
|
||||
import { createSvelteTable, FlexRender } from "$lib/components/ui/data-table/index.js";
|
||||
import * as Table from "$lib/components/ui/table/index.js";
|
||||
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[];
|
||||
loading: boolean;
|
||||
hasMore: boolean;
|
||||
loadMore: () => void;
|
||||
};
|
||||
type DataTableProps<TData, TValue> = {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
data: TData[];
|
||||
pageCount: number;
|
||||
totalItems: number;
|
||||
};
|
||||
|
||||
let {
|
||||
data,
|
||||
columns,
|
||||
loading,
|
||||
hasMore,
|
||||
loadMore
|
||||
}: DataTableProps<TData, TValue> = $props();
|
||||
let {
|
||||
data,
|
||||
columns,
|
||||
pageCount,
|
||||
totalItems
|
||||
}: DataTableProps<TData, TValue> = $props();
|
||||
|
||||
const table = createSvelteTable({
|
||||
get data() {
|
||||
return data;
|
||||
},
|
||||
columns,
|
||||
getCoreRowModel: getCoreRowModel()
|
||||
});
|
||||
const table = createSvelteTable({
|
||||
get data() { return data; },
|
||||
get columns() { return columns; },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
get pageCount() { return pageCount; },
|
||||
});
|
||||
|
||||
let scrollContainer = $state<HTMLDivElement>();
|
||||
let loadingTrigger = $state<HTMLDivElement>();
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}
|
||||
|
||||
// Intersection Observer para detectar cuando el usuario llega al final
|
||||
onMount(() => {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const [entry] = entries;
|
||||
if (entry.isIntersecting && hasMore && !loading) {
|
||||
loadMore();
|
||||
}
|
||||
},
|
||||
{
|
||||
root: scrollContainer,
|
||||
threshold: 0.1
|
||||
}
|
||||
);
|
||||
|
||||
if (loadingTrigger) {
|
||||
observer.observe(loadingTrigger);
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
});
|
||||
const currentPage = $derived(Number($page.url.searchParams.get('page') || 1));
|
||||
</script>
|
||||
|
||||
<div class="w-full">
|
||||
<div class="rounded-md border max-h-[600px] overflow-y-auto" bind:this={scrollContainer}>
|
||||
<Table.Root>
|
||||
<Table.Header class="sticky top-0 bg-background z-10">
|
||||
{#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}
|
||||
|
||||
<!-- Loading Trigger - Se activa cuando es visible -->
|
||||
{#if hasMore}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-20 text-center">
|
||||
<div bind:this={loadingTrigger}>
|
||||
{#if loading}
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent"></div>
|
||||
<span class="text-muted-foreground text-sm">Cargando más...</span>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="text-muted-foreground text-sm">
|
||||
Desplázate para cargar más
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/if}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
<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 px-2">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} registros
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { ClassificationConcept } from '$lib/api/dashboard/a76/general_catalogs/classification-concepts';
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<ClassificationConcept>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'classification',
|
||||
header: 'Clasificación',
|
||||
cell: ({ row }) => row.original.classification || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { deleteClassificationConcept, type ClassificationConcept } from "$lib/api/dashboard/a76/general_catalogs/classification-concepts";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from "$lib/components/dashboard/general_catalogs/classification/create-edit-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: ClassificationConcept;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm(`¿Estás seguro de eliminar la clasificación "${item.classification}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
alert('❌ Error: No hay una compañía seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteClassificationConcept(item.id, companyStore.activeCompany.id);
|
||||
|
||||
if (response.error) {
|
||||
alert(`❌ Error al eliminar:\n\n${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Éxito
|
||||
if (response.status === 204 || response.status === 200 || !response.error) {
|
||||
alert(`✅ Clasificación "${item.classification}" eliminada correctamente`);
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
|
||||
alert(`❌ Error al eliminar:\n\n${errorMsg}`);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={item}
|
||||
onSuccess={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; },
|
||||
get columns() { return columns; },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
get pageCount() { return pageCount; },
|
||||
});
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}
|
||||
|
||||
const currentPage = $derived(Number($page.url.searchParams.get('page') || 1));
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4 px-2">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} registros
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -18,7 +18,7 @@
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm(`¿Estás seguro de eliminar la empresa "${item.name}"?`)) {
|
||||
if (!confirm(`¿Estás seguro de eliminar la empresa "${item.name}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -28,16 +28,23 @@
|
||||
try {
|
||||
const response = await deleteCompany(item.id);
|
||||
|
||||
// Si hay error en la respuesta
|
||||
if (response.error) {
|
||||
alert(`Error al eliminar: ${response.error}`);
|
||||
alert(`❌ Error al eliminar:\n\n${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
// Éxito (status 204 o 200)
|
||||
if (response.status === 204 || response.status === 200 || !response.error) {
|
||||
alert(`✅ Empresa "${item.name}" eliminada correctamente`);
|
||||
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
alert('Error al eliminar el registro');
|
||||
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
|
||||
alert(`❌ Error al eliminar:\n\n${errorMsg}`);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
@@ -55,7 +62,7 @@
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<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";
|
||||
|
||||
// Props exactos que manda tu página de Companies
|
||||
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; },
|
||||
get columns() { return columns; },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
get pageCount() { return pageCount; },
|
||||
});
|
||||
|
||||
// Función para navegar cambiando la URL ?page=X
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url, { keepFocus: true, noScroll: true }); // Truco: noScroll evita saltos feos
|
||||
}
|
||||
|
||||
// Helper para saber la página actual
|
||||
const currentPage = $derived(Number($page.url.searchParams.get('page') || 1));
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4 px-2">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} registros
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { Concept } from '$lib/api/dashboard/a76/general_catalogs/concepts';
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Concept>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
header: 'Código',
|
||||
cell: ({ row }) => row.original.code || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'type',
|
||||
header: 'Tipo',
|
||||
cell: ({ row }) => row.original.type || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'section',
|
||||
header: 'Sección',
|
||||
cell: ({ row }) => row.original.section?.toString() || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { deleteConcept, type Concept } from "$lib/api/dashboard/a76/general_catalogs/concepts";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: Concept;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm(`¿Estás seguro de eliminar el concepto "${item.code}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
alert('❌ Error: No hay una compañía seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteConcept(item.id, companyStore.activeCompany.id);
|
||||
|
||||
if (response.error) {
|
||||
alert(`❌ Error al eliminar:\n\n${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Éxito
|
||||
if (response.status === 204 || response.status === 200 || !response.error) {
|
||||
alert(`✅ Concepto "${item.code}" eliminado correctamente`);
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
|
||||
alert(`❌ Error al eliminar:\n\n${errorMsg}`);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={item}
|
||||
onSuccess={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; },
|
||||
get columns() { return columns; },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
get pageCount() { return pageCount; },
|
||||
});
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}
|
||||
|
||||
const currentPage = $derived(Number($page.url.searchParams.get('page') || 1));
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4 px-2">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} registros
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,39 @@
|
||||
import type { CustomsBrokerConcept } from '$lib/api/dashboard/a76/general_catalogs/customs_broker_concepts';
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<CustomsBrokerConcept>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
header: 'Código',
|
||||
cell: ({ row }) => row.original.code || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'type',
|
||||
header: 'Tipo',
|
||||
cell: ({ row }) => row.original.type || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'section',
|
||||
header: 'Sección',
|
||||
cell: ({ row }) => row.original.section?.toString() || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { deleteCustomsBrokerConcept, type CustomsBrokerConcept } from "$lib/api/dashboard/a76/general_catalogs/customs_broker_concepts";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from "./create-edite-dialoge.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: CustomsBrokerConcept;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm(`¿Estás seguro de eliminar el concepto "${item.code}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
alert('❌ Error: No hay una compañía seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteCustomsBrokerConcept(item.id, companyStore.activeCompany.id);
|
||||
|
||||
if (response.error) {
|
||||
alert(`❌ Error al eliminar:\n\n${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Éxito
|
||||
if (response.status === 204 || response.status === 200 || !response.error) {
|
||||
alert(`✅ Concepto "${item.code}" eliminado correctamente`);
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
|
||||
alert(`❌ Error al eliminar:\n\n${errorMsg}`);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={item}
|
||||
onSuccess={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; },
|
||||
get columns() { return columns; },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
get pageCount() { return pageCount; },
|
||||
});
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}
|
||||
|
||||
const currentPage = $derived(Number($page.url.searchParams.get('page') || 1));
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4 px-2">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} registros
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,13 +71,17 @@
|
||||
|
||||
let response;
|
||||
|
||||
|
||||
if (isEdit && item) {
|
||||
response = await updateLegend(item.id, dataToSend, companyId);
|
||||
} else {
|
||||
response = await createLegend(dataToSend, companyId);
|
||||
}
|
||||
|
||||
// Verificar si hubo error
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
open = false;
|
||||
if (onSuccess) onSuccess();
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { Legend } from '$lib/api/dashboard/a76/general_catalogs/legends';
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<Legend>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
header: 'Código',
|
||||
cell: ({ row }) => row.original.code?.toString() || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<script lang="ts">
|
||||
import { Button } from "$lib/components/ui/button";
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
import { deleteLegend, type Legend } from "$lib/api/dashboard/a76/general_catalogs/legends";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateDialog from '$lib/components/dashboard/general_catalogs/legend/create-edite-dialoge.svelte';
|
||||
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: Legend;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm(`¿Estás seguro de eliminar la leyenda "${item.code}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
alert('❌ Error: No hay una compañía seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deleteLegend(item.id, companyStore.activeCompany.id);
|
||||
|
||||
if (response.error) {
|
||||
alert(`❌ Error al eliminar:\n\n${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Éxito
|
||||
if (response.status === 204 || response.status === 200 || !response.error) {
|
||||
alert(`✅ Leyenda "${item.code}" eliminada correctamente`);
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
|
||||
alert(`❌ Error al eliminar:\n\n${errorMsg}`);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateDialog
|
||||
bind:open={dialogOpen}
|
||||
item={item}
|
||||
onSuccess={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; },
|
||||
get columns() { return columns; },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
get pageCount() { return pageCount; },
|
||||
});
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}
|
||||
|
||||
const currentPage = $derived(Number($page.url.searchParams.get('page') || 1));
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4 px-2">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} registros
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,14 +90,13 @@
|
||||
conversion_factor: formData.conversion_factor ? Number(formData.conversion_factor) : null,
|
||||
publication_date: dateInt // Mandamos el INT que espera Python
|
||||
};
|
||||
|
||||
let response;
|
||||
|
||||
// 👇 companyId por fuera
|
||||
if (isEdit && item) {
|
||||
response = await updateMultiCurrencyType(item.id, dataToSend, companyId);
|
||||
await updateMultiCurrencyType(item.id, dataToSend, companyId);
|
||||
alert(`✅ Tipo de moneda múltiple actualizado correctamente`);
|
||||
} else {
|
||||
response = await createMultiCurrencyType(dataToSend, companyId);
|
||||
await createMultiCurrencyType(dataToSend, companyId);
|
||||
alert(`✅ Tipo de moneda múltiple creado correctamente`);
|
||||
}
|
||||
|
||||
open = false;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { UnitConversion } from '$lib/api/dashboard/a76/general_catalogs/unit-conversions';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitConversion>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'from_unit_code',
|
||||
header: 'Desde código'
|
||||
},
|
||||
{
|
||||
accessorKey: 'to_unit_code',
|
||||
header: 'Hacia código'
|
||||
},
|
||||
{
|
||||
accessorKey: 'conversion_factor',
|
||||
header: 'Factor de conversión'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
cell: ({ row }) => {
|
||||
return {
|
||||
component: DataTableActions,
|
||||
props: {
|
||||
conversion: row.original,
|
||||
onSuccess
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<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 { createUnitConversion, updateUnitConversion, type UnitConversion } from "$lib/api/dashboard/a76/general_catalogs/unit-conversions";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
conversion = null,
|
||||
mode = 'create',
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
conversion?: UnitConversion | null;
|
||||
mode?: 'create' | 'edit';
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(mode === 'edit');
|
||||
const title = $derived(isEdit ? "Editar Conversión" : "Nueva Conversión");
|
||||
|
||||
let formData = $state({
|
||||
from_unit_code: '',
|
||||
to_unit_code: '',
|
||||
conversion_factor: ''
|
||||
});
|
||||
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
if (conversion) {
|
||||
formData = {
|
||||
from_unit_code: conversion.from_unit_code || '',
|
||||
to_unit_code: conversion.to_unit_code || '',
|
||||
conversion_factor: conversion.conversion_factor.toString() || ''
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
from_unit_code: '',
|
||||
to_unit_code: '',
|
||||
conversion_factor: ''
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) throw new Error('No hay una compañía seleccionada');
|
||||
|
||||
if (!formData.from_unit_code.trim()) throw new Error('El código origen es requerido');
|
||||
if (!formData.to_unit_code.trim()) throw new Error('El código destino es requerido');
|
||||
if (!formData.conversion_factor || formData.conversion_factor === '') throw new Error('El factor de conversión es requerido');
|
||||
|
||||
const dataToSend = {
|
||||
from_unit_code: formData.from_unit_code.trim(),
|
||||
to_unit_code: formData.to_unit_code.trim(),
|
||||
conversion_factor: parseFloat(formData.conversion_factor)
|
||||
};
|
||||
|
||||
if (isNaN(dataToSend.conversion_factor)) {
|
||||
throw new Error('El factor de conversión debe ser un número válido');
|
||||
}
|
||||
|
||||
if (isEdit && conversion) {
|
||||
await updateUnitConversion(conversion.id, dataToSend, companyId);
|
||||
alert(`✅ Conversión "${dataToSend.from_unit_code} → ${dataToSend.to_unit_code}" actualizada correctamente`);
|
||||
} else {
|
||||
await createUnitConversion(dataToSend, companyId);
|
||||
alert(`✅ Conversión "${dataToSend.from_unit_code} → ${dataToSend.to_unit_code}" creada correctamente`);
|
||||
}
|
||||
|
||||
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="p-3 text-sm text-destructive bg-destructive/10 rounded-md">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid gap-4 py-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="from_unit_code">Código origen <span class="text-destructive">*</span></Label>
|
||||
<Input
|
||||
id="from_unit_code"
|
||||
bind:value={formData.from_unit_code}
|
||||
maxlength={5}
|
||||
disabled={loading}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="to_unit_code">Código destino <span class="text-destructive">*</span></Label>
|
||||
<Input
|
||||
id="to_unit_code"
|
||||
bind:value={formData.to_unit_code}
|
||||
maxlength={5}
|
||||
disabled={loading}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="conversion_factor">Factor de conversión <span class="text-destructive">*</span></Label>
|
||||
<Input
|
||||
id="conversion_factor"
|
||||
type="number"
|
||||
step="0.000001"
|
||||
bind:value={formData.conversion_factor}
|
||||
disabled={loading}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</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 { deleteUnitConversion, type UnitConversion } from "$lib/api/dashboard/a76/general_catalogs/unit-conversions";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
|
||||
let {
|
||||
conversion,
|
||||
onSuccess
|
||||
}: {
|
||||
conversion: UnitConversion;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm(`¿Estás seguro de eliminar la conversión "${conversion.from_unit_code} → ${conversion.to_unit_code}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
alert('❌ Error: No hay una compañía seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
await deleteUnitConversion(conversion.id, companyStore.activeCompany.id);
|
||||
alert(`✅ Conversión "${conversion.from_unit_code} → ${conversion.to_unit_code}" eliminada correctamente`);
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
} catch (e) {
|
||||
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
|
||||
alert(`❌ Error al eliminar:\n\n${errorMsg}`);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
conversion={conversion}
|
||||
mode="edit"
|
||||
{onSuccess}
|
||||
/>
|
||||
@@ -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; },
|
||||
get columns() { return columns; },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
get pageCount() { return pageCount; },
|
||||
});
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}
|
||||
|
||||
const currentPage = $derived(Number($page.url.searchParams.get('page') || 1));
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4 px-2">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} registros
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { UnitOfMeasureACE } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureACE>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
header: 'Código',
|
||||
cell: ({ row }) => row.original.code || '-'
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: ({ row }) => row.original.description || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
item: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<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/general_catalogs/units-of-measure";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
|
||||
let {
|
||||
open = $bindable(false),
|
||||
item = null,
|
||||
onSuccess
|
||||
}: {
|
||||
open: boolean;
|
||||
item?: UnitOfMeasureACE | null;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
const isEdit = $derived(!!item);
|
||||
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 (item) {
|
||||
formData = {
|
||||
code: item.code || '',
|
||||
description: item.description || ''
|
||||
};
|
||||
} else {
|
||||
formData = {
|
||||
code: '',
|
||||
description: ''
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) throw new Error('No hay una compañía seleccionada');
|
||||
|
||||
if (!formData.code.trim()) throw new Error('El código es requerido');
|
||||
|
||||
const dataToSend = {
|
||||
code: formData.code.trim(),
|
||||
description: formData.description.trim() || null
|
||||
};
|
||||
|
||||
let response;
|
||||
if (isEdit && item) {
|
||||
response = await updateUnitOfMeasureACE(item.id, dataToSend, companyId);
|
||||
} else {
|
||||
response = await createUnitOfMeasureACE(dataToSend, companyId);
|
||||
}
|
||||
|
||||
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="p-3 text-sm text-destructive bg-destructive/10 rounded-md">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="grid gap-4 py-4">
|
||||
<div class="grid gap-2">
|
||||
<Label for="code">Código <span class="text-destructive">*</span></Label>
|
||||
<Input
|
||||
id="code"
|
||||
bind:value={formData.code}
|
||||
maxlength={4}
|
||||
disabled={loading}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2">
|
||||
<Label for="description">Descripción</Label>
|
||||
<Input
|
||||
id="description"
|
||||
bind:value={formData.description}
|
||||
maxlength={49}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => open = false} disabled={loading}>
|
||||
Cancelar
|
||||
</Button>
|
||||
<Button type="submit" disabled={loading}>
|
||||
{loading ? 'Guardando...' : isEdit ? 'Actualizar' : 'Crear'}
|
||||
</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,86 @@
|
||||
<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/general_catalogs/units-of-measure";
|
||||
import { companyStore } from "$lib/stores/company.svelte";
|
||||
import { EllipsisVertical, Pencil, LoaderCircle, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from "./create-edit-dialog.svelte";
|
||||
|
||||
let {
|
||||
item,
|
||||
onSuccess
|
||||
}: {
|
||||
item: UnitOfMeasureACE;
|
||||
onSuccess?: () => void;
|
||||
} = $props();
|
||||
|
||||
let loading = $state(false);
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm(`¿Estás seguro de eliminar la unidad ACE "${item.code}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
alert('❌ Error: No hay una compañía seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
loading = true;
|
||||
|
||||
try {
|
||||
const response = await deleteUnitOfMeasureACE(item.id, companyStore.activeCompany.id);
|
||||
|
||||
if (response.error) {
|
||||
alert(`❌ Error al eliminar:\n\n${response.error}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status === 204 || response.status === 200 || !response.error) {
|
||||
alert(`✅ Unidad ACE "${item.code}" eliminada correctamente`);
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
const errorMsg = e instanceof Error ? e.message : 'Error desconocido al eliminar el registro';
|
||||
alert(`❌ Error al eliminar:\n\n${errorMsg}`);
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger>
|
||||
{#snippet child({ props })}
|
||||
<Button {...props} variant="ghost" size="icon" class="h-8 w-8 p-0">
|
||||
<span class="sr-only">Abrir menú</span>
|
||||
<EllipsisVertical class="h-4 w-4" />
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>Acciones</DropdownMenu.Label>
|
||||
<DropdownMenu.Item onclick={() => dialogOpen = true}>
|
||||
<Pencil class="mr-2 h-4 w-4" />
|
||||
Editar
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item class="text-destructive focus:text-destructive" onclick={handleDelete} disabled={loading}>
|
||||
{#if loading}
|
||||
<LoaderCircle class="mr-2 h-4 w-4 animate-spin" />
|
||||
{:else}
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
{/if}
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={item}
|
||||
onSuccess={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; },
|
||||
get columns() { return columns; },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
get pageCount() { return pageCount; },
|
||||
});
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}
|
||||
|
||||
const currentPage = $derived(Number($page.url.searchParams.get('page') || 1));
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4 px-2">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} registros
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { ColumnDef } from "@tanstack/table-core";
|
||||
import type { UnitOfMeasureAmerican } from "$lib/api/dashboard/a76/general_catalogs/units-of-measure";
|
||||
import { renderComponent } from "$lib/components/ui/data-table/index.js";
|
||||
import DataTableActions from "./data-table-actions.svelte";
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureAmerican>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "code",
|
||||
header: "Código",
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "Descripción",
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) =>
|
||||
renderComponent(DataTableActions, {
|
||||
unit: row.original,
|
||||
onSuccess
|
||||
}),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<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 type {
|
||||
UnitOfMeasureAmerican,
|
||||
UnitOfMeasureAmericanCreate,
|
||||
UnitOfMeasureAmericanUpdate
|
||||
} from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import {
|
||||
createUnitOfMeasureAmerican,
|
||||
updateUnitOfMeasureAmerican
|
||||
} from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
unit?: UnitOfMeasureAmerican;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
let { open = $bindable(), unit, onSuccess }: Props = $props();
|
||||
|
||||
let formData = $state({
|
||||
code: '',
|
||||
description: ''
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (unit) {
|
||||
formData = {
|
||||
code: unit.code,
|
||||
description: unit.description || ''
|
||||
};
|
||||
} else {
|
||||
formData = { code: '', description: '' };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
const activeCompanyId = companyStore.activeCompany?.id;
|
||||
if (!activeCompanyId) {
|
||||
alert('No hay una compañía activa seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
const data: UnitOfMeasureAmericanCreate | UnitOfMeasureAmericanUpdate = {
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
};
|
||||
|
||||
const response = unit
|
||||
? await updateUnitOfMeasureAmerican(unit.id, data, activeCompanyId)
|
||||
: await createUnitOfMeasureAmerican(data, activeCompanyId);
|
||||
|
||||
if (response.error) {
|
||||
alert(response.error.detail || 'Error al guardar');
|
||||
} else {
|
||||
alert(unit ? 'Unidad actualizada' : 'Unidad creada');
|
||||
open = false;
|
||||
onSuccess?.();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[425px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{unit ? 'Editar' : 'Crear'} Unidad Americana</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
<form onsubmit={handleSubmit} class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="code">Código * (máx. 3 caracteres)</Label>
|
||||
<Input id="code" bind:value={formData.code} required maxlength="3" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="description">Descripción (máx. 40 caracteres)</Label>
|
||||
<Input id="description" bind:value={formData.description} maxlength="40" />
|
||||
</div>
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => (open = false)}>Cancelar</Button>
|
||||
<Button type="submit">Guardar</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,60 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { MoreHorizontal, Pencil, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from './create-edit-dialog.svelte';
|
||||
import type { UnitOfMeasureAmerican } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { deleteUnitOfMeasureAmerican } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
interface Props {
|
||||
unit: UnitOfMeasureAmerican;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
let { unit, onSuccess }: Props = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm('¿Estás seguro de eliminar esta unidad?')) return;
|
||||
|
||||
const activeCompanyId = companyStore.activeCompany?.id;
|
||||
if (!activeCompanyId) {
|
||||
alert('No hay una compañía activa seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await deleteUnitOfMeasureAmerican(unit.id, activeCompanyId);
|
||||
if (response.error) {
|
||||
alert(response.error.detail || 'Error al eliminar');
|
||||
} else if (response.status === 204 || response.status === 200) {
|
||||
alert('Unidad eliminada');
|
||||
onSuccess?.();
|
||||
}
|
||||
}
|
||||
</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>
|
||||
<MoreHorizontal 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 onclick={handleDelete}>
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog bind:open={dialogOpen} {unit} {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; },
|
||||
get columns() { return columns; },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
get pageCount() { return pageCount; },
|
||||
});
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}
|
||||
|
||||
const currentPage = $derived(Number($page.url.searchParams.get('page') || 1));
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4 px-2">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} registros
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { ColumnDef } from "@tanstack/table-core";
|
||||
import type { UnitOfMeasureCustoms } from "$lib/api/dashboard/a76/general_catalogs/units-of-measure";
|
||||
import { renderComponent } from "$lib/components/ui/data-table/index.js";
|
||||
import DataTableActions from "./data-table-actions.svelte";
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureCustoms>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "code",
|
||||
header: "Código",
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "Descripción",
|
||||
},
|
||||
{
|
||||
accessorKey: "scaii_unit_code",
|
||||
header: "Código SCAII",
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) =>
|
||||
renderComponent(DataTableActions, {
|
||||
unit: row.original,
|
||||
onSuccess
|
||||
}),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<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 type {
|
||||
UnitOfMeasureCustoms,
|
||||
UnitOfMeasureCustomsCreate,
|
||||
UnitOfMeasureCustomsUpdate
|
||||
} from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import {
|
||||
createUnitOfMeasureCustoms,
|
||||
updateUnitOfMeasureCustoms
|
||||
} from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
unit?: UnitOfMeasureCustoms;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
let { open = $bindable(), unit, onSuccess }: Props = $props();
|
||||
|
||||
let formData = $state({
|
||||
code: '',
|
||||
description: '',
|
||||
scaii_unit_code: ''
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (unit) {
|
||||
formData = {
|
||||
code: unit.code,
|
||||
description: unit.description || '',
|
||||
scaii_unit_code: unit.scaii_unit_code || ''
|
||||
};
|
||||
} else {
|
||||
formData = { code: '', description: '', scaii_unit_code: '' };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
const activeCompanyId = companyStore.activeCompany?.id;
|
||||
if (!activeCompanyId) {
|
||||
alert('No hay una compañía activa seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
const data: UnitOfMeasureCustomsCreate | UnitOfMeasureCustomsUpdate = {
|
||||
code: formData.code,
|
||||
description: formData.description || null,
|
||||
scaii_unit_code: formData.scaii_unit_code || null
|
||||
};
|
||||
|
||||
const response = unit
|
||||
? await updateUnitOfMeasureCustoms(unit.id, data, activeCompanyId)
|
||||
: await createUnitOfMeasureCustoms(data, activeCompanyId);
|
||||
|
||||
if (response.error) {
|
||||
alert(response.error.detail || 'Error al guardar');
|
||||
} else {
|
||||
alert(unit ? 'Unidad actualizada' : 'Unidad creada');
|
||||
open = false;
|
||||
onSuccess?.();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[425px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{unit ? 'Editar' : 'Crear'} Unidad Aduanas MEX</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
<form onsubmit={handleSubmit} class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="code">Código * (máx. 2 caracteres)</Label>
|
||||
<Input id="code" bind:value={formData.code} required maxlength="2" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="description">Descripción (máx. 20 caracteres)</Label>
|
||||
<Input id="description" bind:value={formData.description} maxlength="20" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="scaii_unit_code">Código SCAII</Label>
|
||||
<Input id="scaii_unit_code" bind:value={formData.scaii_unit_code} maxlength="5" />
|
||||
</div>
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => (open = false)}>Cancelar</Button>
|
||||
<Button type="submit">Guardar</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,60 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { MoreHorizontal, Pencil, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from './create-edit-dialog.svelte';
|
||||
import type { UnitOfMeasureCustoms } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { deleteUnitOfMeasureCustoms } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
interface Props {
|
||||
unit: UnitOfMeasureCustoms;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
let { unit, onSuccess }: Props = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm('¿Estás seguro de eliminar esta unidad?')) return;
|
||||
|
||||
const activeCompanyId = companyStore.activeCompany?.id;
|
||||
if (!activeCompanyId) {
|
||||
alert('No hay una compañía activa seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await deleteUnitOfMeasureCustoms(unit.id, activeCompanyId);
|
||||
if (response.error) {
|
||||
alert(response.error.detail || 'Error al eliminar');
|
||||
} else if (response.status === 204 || response.status === 200) {
|
||||
alert('Unidad eliminada');
|
||||
onSuccess?.();
|
||||
}
|
||||
}
|
||||
</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>
|
||||
<MoreHorizontal 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 onclick={handleDelete}>
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog bind:open={dialogOpen} {unit} {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; },
|
||||
get columns() { return columns; },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
get pageCount() { return pageCount; },
|
||||
});
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}
|
||||
|
||||
const currentPage = $derived(Number($page.url.searchParams.get('page') || 1));
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4 px-2">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} registros
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { ColumnDef } from '@tanstack/table-core';
|
||||
import type { UnitOfMeasureGeneral } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { renderComponent } from '$lib/components/ui/data-table';
|
||||
import DataTableActions from './data-table-actions.svelte';
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureGeneral>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'code',
|
||||
header: 'Código',
|
||||
cell: (info) => info.getValue()
|
||||
},
|
||||
{
|
||||
accessorKey: 'description',
|
||||
header: 'Descripción',
|
||||
cell: (info) => info.getValue() || '-'
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Acciones',
|
||||
cell: ({ row }) => {
|
||||
return renderComponent(DataTableActions, {
|
||||
unit: row.original,
|
||||
onSuccess
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<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 type {
|
||||
UnitOfMeasureGeneral,
|
||||
UnitOfMeasureGeneralCreate,
|
||||
UnitOfMeasureGeneralUpdate
|
||||
} from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import {
|
||||
createUnitOfMeasureGeneral,
|
||||
updateUnitOfMeasureGeneral
|
||||
} from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
unit?: UnitOfMeasureGeneral;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
let { open = $bindable(), unit, onSuccess }: Props = $props();
|
||||
|
||||
let formData = $state({
|
||||
code: '',
|
||||
description: ''
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (unit) {
|
||||
formData = {
|
||||
code: unit.code,
|
||||
description: unit.description || ''
|
||||
};
|
||||
} else {
|
||||
formData = { code: '', description: '' };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
const activeCompanyId = companyStore.activeCompany?.id;
|
||||
if (!activeCompanyId) {
|
||||
alert('No hay una compañía activa seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
const data: UnitOfMeasureGeneralCreate | UnitOfMeasureGeneralUpdate = {
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
};
|
||||
|
||||
const response = unit
|
||||
? await updateUnitOfMeasureGeneral(unit.id, data, activeCompanyId)
|
||||
: await createUnitOfMeasureGeneral(data, activeCompanyId);
|
||||
|
||||
if (response.error) {
|
||||
alert(response.error.detail || 'Error al guardar');
|
||||
} else {
|
||||
alert(unit ? 'Unidad actualizada' : 'Unidad creada');
|
||||
open = false;
|
||||
onSuccess?.();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[425px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{unit ? 'Editar' : 'Crear'} Unidad General</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
<form onsubmit={handleSubmit} class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="code">Código *</Label>
|
||||
<Input id="code" bind:value={formData.code} required />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="description">Descripción</Label>
|
||||
<Input id="description" bind:value={formData.description} />
|
||||
</div>
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => (open = false)}>Cancelar</Button>
|
||||
<Button type="submit">Guardar</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,60 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { MoreHorizontal, Pencil, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from './create-edit-dialog.svelte';
|
||||
import type { UnitOfMeasureGeneral } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { deleteUnitOfMeasureGeneral } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
interface Props {
|
||||
unit: UnitOfMeasureGeneral;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
let { unit, onSuccess }: Props = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm('¿Estás seguro de eliminar esta unidad?')) return;
|
||||
|
||||
const activeCompanyId = companyStore.activeCompany?.id;
|
||||
if (!activeCompanyId) {
|
||||
alert('No hay una compañía activa seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await deleteUnitOfMeasureGeneral(unit.id, activeCompanyId);
|
||||
if (response.error) {
|
||||
alert(response.error.detail || 'Error al eliminar');
|
||||
} else if (response.status === 204 || response.status === 200) {
|
||||
alert('Unidad eliminada');
|
||||
onSuccess?.();
|
||||
}
|
||||
}
|
||||
</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>
|
||||
<MoreHorizontal 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 onclick={handleDelete}>
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog bind:open={dialogOpen} {unit} {onSuccess} />
|
||||
@@ -0,0 +1,107 @@
|
||||
<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; },
|
||||
get columns() { return columns; },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
get pageCount() { return pageCount; },
|
||||
});
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}
|
||||
|
||||
const currentPage = $derived(Number($page.url.searchParams.get('page') || 1));
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4 px-2">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} registros
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { ColumnDef } from "@tanstack/table-core";
|
||||
import type { UnitOfMeasureOMA } from "$lib/api/dashboard/a76/general_catalogs/units-of-measure";
|
||||
import { renderComponent } from "$lib/components/ui/data-table/index.js";
|
||||
import DataTableActions from "./data-table-actions.svelte";
|
||||
|
||||
export function createColumns(onSuccess?: () => void): ColumnDef<UnitOfMeasureOMA>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: "code",
|
||||
header: "Código",
|
||||
},
|
||||
{
|
||||
accessorKey: "description",
|
||||
header: "Descripción",
|
||||
},
|
||||
{
|
||||
id: "actions",
|
||||
cell: ({ row }) =>
|
||||
renderComponent(DataTableActions, {
|
||||
unit: row.original,
|
||||
onSuccess
|
||||
}),
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<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 type {
|
||||
UnitOfMeasureOMA,
|
||||
UnitOfMeasureOMACreate,
|
||||
UnitOfMeasureOMAUpdate
|
||||
} from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import {
|
||||
createUnitOfMeasureOMA,
|
||||
updateUnitOfMeasureOMA
|
||||
} from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
unit?: UnitOfMeasureOMA;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
let { open = $bindable(), unit, onSuccess }: Props = $props();
|
||||
|
||||
let formData = $state({
|
||||
code: '',
|
||||
description: ''
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
if (unit) {
|
||||
formData = {
|
||||
code: unit.code,
|
||||
description: unit.description || ''
|
||||
};
|
||||
} else {
|
||||
formData = { code: '', description: '' };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit(e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
const activeCompanyId = companyStore.activeCompany?.id;
|
||||
if (!activeCompanyId) {
|
||||
alert('No hay una compañía activa seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
const data: UnitOfMeasureOMACreate | UnitOfMeasureOMAUpdate = {
|
||||
code: formData.code,
|
||||
description: formData.description || null
|
||||
};
|
||||
|
||||
const response = unit
|
||||
? await updateUnitOfMeasureOMA(unit.id, data, activeCompanyId)
|
||||
: await createUnitOfMeasureOMA(data, activeCompanyId);
|
||||
|
||||
if (response.error) {
|
||||
alert(response.error.detail || 'Error al guardar');
|
||||
} else {
|
||||
alert(unit ? 'Unidad actualizada' : 'Unidad creada');
|
||||
open = false;
|
||||
onSuccess?.();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Content class="sm:max-w-[425px]">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>{unit ? 'Editar' : 'Crear'} Unidad OMA</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
<form onsubmit={handleSubmit} class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<Label for="code">Código * (máx. 10 caracteres)</Label>
|
||||
<Input id="code" bind:value={formData.code} required maxlength="10" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="description">Descripción (máx. 200 caracteres)</Label>
|
||||
<Input id="description" bind:value={formData.description} maxlength="200" />
|
||||
</div>
|
||||
<Dialog.Footer>
|
||||
<Button type="button" variant="outline" onclick={() => (open = false)}>Cancelar</Button>
|
||||
<Button type="submit">Guardar</Button>
|
||||
</Dialog.Footer>
|
||||
</form>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -0,0 +1,60 @@
|
||||
<script lang="ts">
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { MoreHorizontal, Pencil, Trash2 } from 'lucide-svelte';
|
||||
import CreateEditDialog from './create-edit-dialog.svelte';
|
||||
import type { UnitOfMeasureOMA } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { deleteUnitOfMeasureOMA } from '$lib/api/dashboard/a76/general_catalogs/units-of-measure';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
|
||||
interface Props {
|
||||
unit: UnitOfMeasureOMA;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
let { unit, onSuccess }: Props = $props();
|
||||
let dialogOpen = $state(false);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm('¿Estás seguro de eliminar esta unidad?')) return;
|
||||
|
||||
const activeCompanyId = companyStore.activeCompany?.id;
|
||||
if (!activeCompanyId) {
|
||||
alert('No hay una compañía activa seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await deleteUnitOfMeasureOMA(unit.id, activeCompanyId);
|
||||
if (response.error) {
|
||||
alert(response.error.detail || 'Error al eliminar');
|
||||
} else if (response.status === 204 || response.status === 200) {
|
||||
alert('Unidad eliminada');
|
||||
onSuccess?.();
|
||||
}
|
||||
}
|
||||
</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>
|
||||
<MoreHorizontal 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 onclick={handleDelete}>
|
||||
<Trash2 class="mr-2 h-4 w-4" />
|
||||
Eliminar
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
<CreateEditDialog bind:open={dialogOpen} {unit} {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; },
|
||||
get columns() { return columns; },
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
get pageCount() { return pageCount; },
|
||||
});
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', newPage.toString());
|
||||
goto(url, { keepFocus: true, noScroll: true });
|
||||
}
|
||||
|
||||
const currentPage = $derived(Number($page.url.searchParams.get('page') || 1));
|
||||
</script>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
|
||||
<Table.Row>
|
||||
{#each headerGroup.headers as header (header.id)}
|
||||
<Table.Head>
|
||||
{#if !header.isPlaceholder}
|
||||
<FlexRender
|
||||
content={header.column.columnDef.header}
|
||||
context={header.getContext()}
|
||||
/>
|
||||
{/if}
|
||||
</Table.Head>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each table.getRowModel().rows as row (row.id)}
|
||||
<Table.Row data-state={row.getIsSelected() && "selected"}>
|
||||
{#each row.getVisibleCells() as cell (cell.id)}
|
||||
<Table.Cell>
|
||||
<FlexRender
|
||||
content={cell.column.columnDef.cell}
|
||||
context={cell.getContext()}
|
||||
/>
|
||||
</Table.Cell>
|
||||
{/each}
|
||||
</Table.Row>
|
||||
{:else}
|
||||
<Table.Row>
|
||||
<Table.Cell colspan={columns.length} class="h-24 text-center">
|
||||
No hay resultados.
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end space-x-2 py-4 px-2">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
Total: {totalItems} registros
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage - 1)}
|
||||
disabled={currentPage <= 1}
|
||||
>
|
||||
Anterior
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onclick={() => handlePageChange(currentPage + 1)}
|
||||
disabled={currentPage >= pageCount}
|
||||
>
|
||||
Siguiente
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -20,7 +20,12 @@
|
||||
let selectedItem = $state<Package | null>(null);
|
||||
|
||||
async function handleDelete() {
|
||||
if (!confirm(`¿Estás seguro de eliminar el bulto "${item.key}"?`)) {
|
||||
if (!confirm(`¿Estás seguro de eliminar el bulto "${item.key}"?\n\nNota: No se puede eliminar si tiene registros relacionados.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!companyStore.activeCompany) {
|
||||
alert('❌ Error: No hay una compañía seleccionada');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -28,7 +33,7 @@
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const response = await deletePackage(item.id);
|
||||
const response = await deletePackage(item.id, companyStore.activeCompany.id);
|
||||
|
||||
if (response.error) {
|
||||
if (response.status === 401) {
|
||||
@@ -38,18 +43,21 @@
|
||||
}, 1500);
|
||||
} else {
|
||||
error = response.error;
|
||||
alert(`Error al eliminar: ${response.error}`);
|
||||
alert(`❌ Error al eliminar:\n\n${response.error}`);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Éxito
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
if (response.status === 204 || response.status === 200 || !response.error) {
|
||||
alert(`✅ Bulto "${item.key}" eliminado correctamente`);
|
||||
if (onSuccess) {
|
||||
onSuccess();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : "Error al eliminar";
|
||||
alert(`Error: ${error}`);
|
||||
alert(`❌ Error: ${error}`);
|
||||
console.error("Error deleting:", e);
|
||||
} finally {
|
||||
loading = false;
|
||||
|
||||
@@ -27,10 +27,14 @@
|
||||
get data() {
|
||||
return data;
|
||||
},
|
||||
columns,
|
||||
get columns() {
|
||||
return columns;
|
||||
},
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
pageCount: pageCount,
|
||||
get pageCount() {
|
||||
return pageCount;
|
||||
},
|
||||
});
|
||||
|
||||
function handlePageChange(newPage: number) {
|
||||
|
||||
@@ -19,7 +19,26 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
if (classification) filters.classification = classification;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters });
|
||||
// Obtener company_id de la cookie o usar el primero disponible
|
||||
const parentData = await parent();
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
if (!companyId) {
|
||||
return {
|
||||
error: 'No se encontró una compañía seleccionada',
|
||||
classifications: { items: [], total: 0, page: 1, page_size: 50, pages: 0 }
|
||||
};
|
||||
}
|
||||
|
||||
const queryParams = new URLSearchParams({
|
||||
company_id: companyId.toString(),
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
const response = await authenticatedFetch(`v1/a76/classification-concepts?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import DataTable from '$lib/components/dashboard/general_catalogs/classification_concepts/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/general_catalogs/classification_concepts/columns';
|
||||
import CreateDialog from '$lib/components/dashboard/general_catalogs/classification/create-edit-dialog.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
@@ -16,10 +17,12 @@
|
||||
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 handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
|
||||
const columns = $derived(createColumns(handleSuccess));
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
@@ -36,11 +39,6 @@
|
||||
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">
|
||||
@@ -74,17 +72,15 @@
|
||||
</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>
|
||||
<DataTable
|
||||
data={data.classifications?.items || []}
|
||||
columns={columns}
|
||||
pageCount={data.classifications?.pages || 0}
|
||||
totalItems={data.classifications?.total || 0}
|
||||
/>
|
||||
<CreateDialog
|
||||
bind:open={dialogOpen}
|
||||
title="Crear Nueva Clasificación de Concepto"
|
||||
on:success={handleSuccess}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,7 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
const rfc = url.searchParams.get('rfc');
|
||||
|
||||
if (name) filters.name = name;
|
||||
if (rfc) filters.rfc = rfc;
|
||||
if (rfc) filters.rfc = rfc;
|
||||
|
||||
// Construir URL con parámetros
|
||||
const queryParams = new URLSearchParams({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import DataTable from '$lib/components/dashboard/units_of_measure/ace/data-table.svelte';
|
||||
import DataTable from '$lib/components/dashboard/general_catalogs/company/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/general_catalogs/company/columns.js';
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/company/create-edit-dialog.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
|
||||
@@ -31,8 +31,28 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
// Obtener company_id de la cookie o usar el primero disponible
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
if (!companyId) {
|
||||
return {
|
||||
error: 'No se encontró una compañía seleccionada',
|
||||
concepts: {
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
pages: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Construir URL con parámetros
|
||||
const queryParams = new URLSearchParams({
|
||||
company_id: companyId.toString(),
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import DataTable from '$lib/components/dashboard/general_catalogs/concepts/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/general_catalogs/concepts/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/concepts/create-edit-dialog.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
@@ -16,12 +17,6 @@
|
||||
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);
|
||||
@@ -76,9 +71,9 @@
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<SimpleDataTable
|
||||
<DataTable
|
||||
data={data.concepts?.items || []}
|
||||
columns={columns}
|
||||
columns={createColumns(handleSuccess)}
|
||||
pageCount={data.concepts?.pages || 0}
|
||||
totalItems={data.concepts?.total || 0}
|
||||
/>
|
||||
@@ -86,6 +81,6 @@
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
on:success={handleSuccess}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,26 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters });
|
||||
// Obtener company_id de la cookie o usar el primero disponible
|
||||
const parentData = await parent();
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
if (!companyId) {
|
||||
return {
|
||||
error: 'No se encontró una compañía seleccionada',
|
||||
concepts: { items: [], total: 0, page: 1, page_size: 50, pages: 0 }
|
||||
};
|
||||
}
|
||||
|
||||
const queryParams = new URLSearchParams({
|
||||
company_id: companyId.toString(),
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
const response = await authenticatedFetch(`v1/a76/customs-broker-concepts?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import DataTable from '$lib/components/dashboard/general_catalogs/customs_broker_concepts/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/general_catalogs/customs_broker_concepts/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/customs_broker_concepts/create-edite-dialoge.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
@@ -16,11 +17,14 @@
|
||||
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' },
|
||||
];
|
||||
// Solo necesitamos una declaración de handleSuccess
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
|
||||
// columns depende de handleSuccess, así que se queda igual
|
||||
const columns = $derived(createColumns(handleSuccess));
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
@@ -37,11 +41,6 @@
|
||||
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">
|
||||
@@ -76,7 +75,7 @@
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<SimpleDataTable
|
||||
<DataTable
|
||||
data={data.concepts?.items || []}
|
||||
columns={columns}
|
||||
pageCount={data.concepts?.pages || 0}
|
||||
@@ -86,6 +85,6 @@
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
on:success={handleSuccess}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2,35 +2,65 @@ import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
return { error: 'No authenticated', exchangeRates: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
if (!accessToken) {
|
||||
return {
|
||||
error: 'No authenticated',
|
||||
exchange_rates: { items: [], total: 0, page: 1, page_size: 10, pages: 0 }
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
const date = url.searchParams.get('date');
|
||||
const localCurrency = url.searchParams.get('local_currency');
|
||||
const foreignCurrency = url.searchParams.get('foreign_currency');
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 10;
|
||||
const filters: Record<string, string> = {};
|
||||
|
||||
if (date) filters.date = date;
|
||||
if (localCurrency) filters.local_currency = localCurrency;
|
||||
if (foreignCurrency) filters.foreign_currency = foreignCurrency;
|
||||
// Obtener company_id
|
||||
const parentData = await parent();
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters });
|
||||
const response = await authenticatedFetch(`v1/a76/exchange-rate?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
if (!companyId) {
|
||||
return {
|
||||
error: 'No company selected',
|
||||
exchange_rates: { items: [], total: 0, page, page_size: pageSize, pages: 0 }
|
||||
};
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', exchangeRates: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
return { exchangeRates: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error loading exchange rates:', error);
|
||||
return { error: 'Error loading', exchangeRates: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
const response = await authenticatedFetch(
|
||||
`v1/a76/exchange-rate/?${queryParams.toString()}`,
|
||||
{
|
||||
method: 'GET',
|
||||
cache: 'no-store'
|
||||
},
|
||||
cookies,
|
||||
fetch
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
return {
|
||||
error: 'Failed to load',
|
||||
exchange_rates: { items: [], total: 0, page, page_size: pageSize, pages: 0 }
|
||||
};
|
||||
}
|
||||
|
||||
return { exchange_rates: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error loading exchange rates:', error);
|
||||
return {
|
||||
error: 'Error loading',
|
||||
exchange_rates: { items: [], total: 0, page: 1, page_size: 10, pages: 0 }
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,192 +1,70 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Label } from '$lib/components/ui/label';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import * as Alert from '$lib/components/ui/alert';
|
||||
import { Plus, Search } from 'lucide-svelte';
|
||||
import DataTable from '$lib/components/dashboard/exchange-rate/data-table.svelte';
|
||||
import CreateEditDialog from '$lib/components/dashboard/exchange-rate/create-edit-dialog.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/exchange-rate/columns';
|
||||
import type { ExchangeRate } from '$lib/api/dashboard/a76/general_catalogs/exchange-rate';
|
||||
import { getExchangeRates } from '$lib/api/dashboard/a76/general_catalogs/exchange-rate';
|
||||
import { companyStore } from '$lib/stores/company.svelte';
|
||||
import { goto, invalidateAll } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { createColumns } from '$lib/components/dashboard/exchange-rate/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/exchange-rate/create-edit-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/exchange-rate/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 } = $props();
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
let allItems = $state<ExchangeRate[]>(data.exchangeRates?.items || []);
|
||||
let loading = $state(false);
|
||||
let error = $state<string | null>(data.error || null);
|
||||
let currentPage = $state(1);
|
||||
const pageSize = 50;
|
||||
let createDialogOpen = $state(false);
|
||||
let loading = $state(false);
|
||||
|
||||
let dialogOpen = $state(false);
|
||||
let editingItem = $state<ExchangeRate | null>(null);
|
||||
const columns = createColumns(() => {
|
||||
refreshData();
|
||||
});
|
||||
|
||||
// Filters
|
||||
let dateFilter = $state('');
|
||||
let localCurrencyFilter = $state('');
|
||||
let foreignCurrencyFilter = $state('');
|
||||
|
||||
let hasMore = $derived(allItems.length >= currentPage * pageSize);
|
||||
|
||||
onMount(() => {
|
||||
// Sync access_token from cookies to localStorage
|
||||
const cookies = document.cookie.split(';');
|
||||
for (const cookie of cookies) {
|
||||
const [name, value] = cookie.trim().split('=');
|
||||
if (name === 'access_token') {
|
||||
localStorage.setItem('access_token', value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Carga inicial reactiva al store
|
||||
if (companyStore.activeCompany) {
|
||||
loadInitialData();
|
||||
}
|
||||
});
|
||||
|
||||
// Efecto reactivo: si cambia la compañía, recargar
|
||||
$effect(() => {
|
||||
if (companyStore.activeCompany?.id) {
|
||||
loadInitialData();
|
||||
}
|
||||
});
|
||||
|
||||
async function loadInitialData() {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId) return;
|
||||
|
||||
allItems = [];
|
||||
currentPage = 1;
|
||||
await loadExchangeRates(1);
|
||||
}
|
||||
|
||||
async function loadExchangeRates(page: number) {
|
||||
const companyId = companyStore.activeCompany?.id;
|
||||
if (!companyId || loading) return;
|
||||
|
||||
loading = true;
|
||||
error = null;
|
||||
|
||||
try {
|
||||
const filters: any = {
|
||||
page,
|
||||
page_size: pageSize
|
||||
};
|
||||
|
||||
if (dateFilter) filters.date = dateFilter;
|
||||
if (localCurrencyFilter) filters.local_currency = localCurrencyFilter;
|
||||
if (foreignCurrencyFilter) filters.foreign_currency = foreignCurrencyFilter;
|
||||
|
||||
const response = await getExchangeRates(companyId, filters);
|
||||
|
||||
if (page === 1) {
|
||||
allItems = response.items;
|
||||
} else {
|
||||
allItems = [...allItems, ...response.items];
|
||||
}
|
||||
currentPage = page;
|
||||
} catch (err: any) {
|
||||
error = err.message || 'Error al cargar los tipos de cambio';
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function loadMore() {
|
||||
if (!loading && hasMore) {
|
||||
loadExchangeRates(currentPage + 1);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
loadInitialData();
|
||||
}
|
||||
|
||||
function handleCreate() {
|
||||
editingItem = null;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
// Esta función se pasa a las columnas
|
||||
function handleEdit(item: ExchangeRate) {
|
||||
editingItem = item;
|
||||
dialogOpen = true;
|
||||
}
|
||||
|
||||
function handleSuccess(item?: ExchangeRate) {
|
||||
loadInitialData();
|
||||
}
|
||||
|
||||
const columns = createColumns(handleEdit); // Pasamos handleEdit en lugar de handleSuccess
|
||||
async function refreshData() {
|
||||
loading = true;
|
||||
// Si no estamos en la página 1, navegar a ella
|
||||
const currentPage = Number($page.url.searchParams.get('page') || 1);
|
||||
if (currentPage !== 1) {
|
||||
const url = new URL($page.url);
|
||||
url.searchParams.set('page', '1');
|
||||
await goto(url, { keepFocus: true, noScroll: true });
|
||||
} else {
|
||||
await invalidateAll();
|
||||
}
|
||||
loading = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Tipos de Cambio - Anexo 76</title>
|
||||
</svelte:head>
|
||||
<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">Tipos de Cambio</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Catálogo de tipos de cambio
|
||||
</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 Tipo de Cambio
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6 p-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold tracking-tight">Tipos de Cambio</h1>
|
||||
<p class="text-muted-foreground">Gestiona los tipos de cambio del sistema</p>
|
||||
</div>
|
||||
|
||||
<Button href="/dashboard/general_catalogs/exchange-rate/new">
|
||||
<Plus class="mr-2 h-4 w-4" />
|
||||
Crear Tipo de Cambio
|
||||
</Button>
|
||||
</div>
|
||||
<Card.Root>
|
||||
<Card.Content class="p-0">
|
||||
<DataTable
|
||||
data={data.exchange_rates?.items || []}
|
||||
{columns}
|
||||
pageCount={data.exchange_rates?.pages || 0}
|
||||
totalItems={data.exchange_rates?.total || 0}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Header>
|
||||
<Card.Title>Filtros</Card.Title>
|
||||
</Card.Header>
|
||||
<Card.Content>
|
||||
<div class="grid gap-4 md:grid-cols-4 items-end">
|
||||
<div class="space-y-2">
|
||||
<Label for="date-filter">Fecha</Label>
|
||||
<Input id="date-filter" type="date" bind:value={dateFilter} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="local-currency-filter">Moneda Local</Label>
|
||||
<Input id="local-currency-filter" placeholder="MXN" bind:value={localCurrencyFilter} />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<Label for="foreign-currency-filter">Moneda Extranjera</Label>
|
||||
<Input id="foreign-currency-filter" placeholder="USD" bind:value={foreignCurrencyFilter} />
|
||||
</div>
|
||||
<Button onclick={handleSearch} class="w-full" variant="secondary">
|
||||
<Search class="mr-2 h-4 w-4" />
|
||||
Buscar
|
||||
</Button>
|
||||
</div>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
{#if error}
|
||||
<Alert.Root variant="destructive">
|
||||
<Alert.Title>Error</Alert.Title>
|
||||
<Alert.Description>{error}</Alert.Description>
|
||||
</Alert.Root>
|
||||
{/if}
|
||||
|
||||
<div class="rounded-md border bg-card">
|
||||
<DataTable
|
||||
data={allItems}
|
||||
{columns}
|
||||
{loading}
|
||||
{hasMore}
|
||||
loadMore={loadMore}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={dialogOpen}
|
||||
item={editingItem}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={refreshData}
|
||||
/>
|
||||
</div>
|
||||
@@ -19,7 +19,26 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters });
|
||||
// Obtener company_id de la cookie o usar el primero disponible
|
||||
const parentData = await parent();
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
if (!companyId) {
|
||||
return {
|
||||
error: 'No se encontró una compañía seleccionada',
|
||||
legends: { items: [], total: 0, page: 1, page_size: 50, pages: 0 }
|
||||
};
|
||||
}
|
||||
|
||||
const queryParams = new URLSearchParams({
|
||||
company_id: companyId.toString(),
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
const response = await authenticatedFetch(`v1/a76/legends?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import DataTable from '$lib/components/dashboard/general_catalogs/legends/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/general_catalogs/legends/columns';
|
||||
import CreateDialog from '$lib/components/dashboard/general_catalogs/legend/create-edite-dialoge.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
@@ -16,10 +17,12 @@
|
||||
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 handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
|
||||
const columns = $derived(createColumns(handleSuccess));
|
||||
|
||||
function handleSearch() {
|
||||
if (!browser) return;
|
||||
@@ -36,11 +39,6 @@
|
||||
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">
|
||||
@@ -74,17 +72,15 @@
|
||||
</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>
|
||||
<DataTable
|
||||
data={data.legends?.items || []}
|
||||
columns={columns}
|
||||
pageCount={data.legends?.pages || 0}
|
||||
totalItems={data.legends?.total || 0}
|
||||
/>
|
||||
|
||||
<CreateDialog
|
||||
bind:open={dialogOpen}
|
||||
on:success={handleSuccess}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -12,15 +12,28 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
const key = url.searchParams.get('key');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
// Obtener company_id
|
||||
const parentData = await parent();
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
if (key) filters.key = key;
|
||||
if (description) filters.description = description;
|
||||
if (!companyId) {
|
||||
return {
|
||||
error: 'No company selected',
|
||||
types: { items: [], total: 0, page, page_size: pageSize, pages: 0 }
|
||||
};
|
||||
}
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters });
|
||||
const response = await authenticatedFetch(`v1/a76/multi-currency-types?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString()
|
||||
});
|
||||
|
||||
const response = await authenticatedFetch(`v1/a76/multi-currency-types/?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', types: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
|
||||
@@ -3,40 +3,19 @@
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import CreateDialog from '$lib/components/dashboard/general_catalogs/multi_currency_types/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 { goto } from '$app/navigation';
|
||||
|
||||
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' },
|
||||
{ key: 'currency_type_code', label: 'Código Moneda' },
|
||||
{ key: 'country_key', label: 'País' },
|
||||
{ key: 'conversion_factor', label: 'Factor Conversión' },
|
||||
{ key: 'publication_date', label: 'Fecha Publicació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 });
|
||||
@@ -57,23 +36,6 @@
|
||||
</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 || []}
|
||||
|
||||
@@ -31,8 +31,28 @@ export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
if (key) filters.key = key;
|
||||
if (description_es) filters.description_es = description_es;
|
||||
|
||||
// Obtener company_id de la cookie o usar el primero disponible
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
if (!companyId) {
|
||||
return {
|
||||
error: 'No se encontró una compañía seleccionada',
|
||||
packages: {
|
||||
items: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
pages: 0
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Construir URL con parámetros
|
||||
const queryParams = new URLSearchParams({
|
||||
company_id: companyId.toString(),
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
|
||||
@@ -2,28 +2,62 @@ import type { PageServerLoad } from './$types';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
return { error: 'No authenticated', conversions: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
if (!accessToken) {
|
||||
return {
|
||||
error: 'No authenticated',
|
||||
conversions: { items: [], total: 0, page: 1, page_size: 50, pages: 0 }
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
try {
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
|
||||
const queryParams = new URLSearchParams({ page: page.toString(), page_size: pageSize.toString(), ...filters });
|
||||
const response = await authenticatedFetch(`v1/a76/unit-conversions?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
// Obtener company_id
|
||||
const parentData = await parent();
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', conversions: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
if (!companyId) {
|
||||
return {
|
||||
error: 'No company selected',
|
||||
conversions: { items: [], total: 0, page, page_size: pageSize, pages: 0 }
|
||||
};
|
||||
}
|
||||
|
||||
return { conversions: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error loading unit conversions:', error);
|
||||
return { error: 'Error loading', conversions: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
const queryParams = new URLSearchParams({
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
company_id: companyId.toString(),
|
||||
...filters
|
||||
});
|
||||
|
||||
const response = await authenticatedFetch(
|
||||
`v1/a76/unit-conversions/?${queryParams.toString()}`,
|
||||
{ method: 'GET' },
|
||||
cookies,
|
||||
fetch
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
return {
|
||||
error: 'Failed to load',
|
||||
conversions: { items: [], total: 0, page, page_size: pageSize, pages: 0 }
|
||||
};
|
||||
}
|
||||
|
||||
return { conversions: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error loading unit conversions:', error);
|
||||
return {
|
||||
error: 'Error loading',
|
||||
conversions: { items: [], total: 0, page: 1, page_size: 50, pages: 0 }
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,53 +1,62 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import SimpleDataTable from '$lib/components/dashboard/general_catalogs/simple-data-table.svelte';
|
||||
import CreateDialogEdit from '$lib/components/dashboard/general_catalogs/unit-conversion/create-edite-dialoge.svelte';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { createColumns } from '$lib/components/dashboard/general_catalogs/unit_conversions/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/unit_conversions/create-edit-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/general_catalogs/unit_conversions/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 } = $props();
|
||||
let dialogOpen = $state(false);
|
||||
let { data }: { data: PageData } = $props();
|
||||
|
||||
const columns = [
|
||||
{ key: 'from_unit_id', label: 'De Unidad' },
|
||||
{ key: 'to_unit_id', label: 'A Unidad' },
|
||||
{ key: 'conversion_factor', label: 'Factor de Conversión' },
|
||||
];
|
||||
let createDialogOpen = $state(false);
|
||||
let loading = $state(false);
|
||||
|
||||
function handleSuccess() {
|
||||
const url = new URL($page.url);
|
||||
goto(url, { invalidateAll: true });
|
||||
}
|
||||
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>
|
||||
<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="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold tracking-tight">Conversiones de Unidades</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Catálogo de conversiones de unidades de medida
|
||||
</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" />
|
||||
Nueva Conversión
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-md border">
|
||||
<SimpleDataTable
|
||||
data={data.conversions?.items || []}
|
||||
columns={columns}
|
||||
pageCount={data.conversions?.pages || 0}
|
||||
totalItems={data.conversions?.total || 0}
|
||||
/>
|
||||
</div>
|
||||
<Card.Root>
|
||||
<Card.Content class="p-0">
|
||||
<DataTable
|
||||
data={data.conversions?.items || []}
|
||||
{columns}
|
||||
pageCount={data.conversions?.pages || 0}
|
||||
totalItems={data.conversions?.total || 0}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<CreateDialogEdit
|
||||
open={dialogOpen}
|
||||
on:close={() => dialogOpen = false}
|
||||
on:success={handleSuccess}
|
||||
/>
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={refreshData}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,57 +1,53 @@
|
||||
import { getServerApiUrl, getAuthTokens } from '$lib/server/api';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url }) => {
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
throw redirect(302, '/login');
|
||||
return { error: 'No authenticated', ace_units: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
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}`
|
||||
}
|
||||
});
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Error fetching ACE units: ${response.status} ${response.statusText}`);
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
// Obtener company_id de la cookie o usar el primero disponible
|
||||
const parentData = await parent();
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
if (!companyId) {
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: `Error: ${response.statusText}`
|
||||
error: 'No se encontró una compañía seleccionada',
|
||||
ace_units: { items: [], total: 0, page: 1, page_size: 50, pages: 0 }
|
||||
};
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
items: data.items,
|
||||
total: data.total,
|
||||
page: data.page,
|
||||
pageSize: data.page_size,
|
||||
pages: data.pages
|
||||
};
|
||||
const queryParams = new URLSearchParams({
|
||||
company_id: companyId.toString(),
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
const response = await authenticatedFetch(`v1/a76/units-of-measure/ace/?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
return { error: 'Failed to load', ace_units: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
|
||||
return { ace_units: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error fetching ACE units:', error);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: 'Failed to connect to server'
|
||||
};
|
||||
console.error('Error loading ACE units:', error);
|
||||
return { error: 'Error loading', ace_units: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<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 { createColumns } from '$lib/components/dashboard/general_catalogs/units_of_measure/ace/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/units_of_measure/ace/create-edit-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/general_catalogs/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';
|
||||
@@ -46,10 +46,10 @@
|
||||
<Card.Root>
|
||||
<Card.Content class="p-0">
|
||||
<DataTable
|
||||
data={data.items}
|
||||
data={data.ace_units?.items || []}
|
||||
{columns}
|
||||
pageCount={data.pages}
|
||||
totalItems={data.total}
|
||||
pageCount={data.ace_units?.pages || 0}
|
||||
totalItems={data.ace_units?.total || 0}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
@@ -1,56 +1,45 @@
|
||||
import { getServerApiUrl, getAuthTokens } from '$lib/server/api';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url }) => {
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
throw redirect(302, '/login');
|
||||
return { error: 'No authenticated', american_units: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
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}`
|
||||
}
|
||||
});
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const parentData = await parent();
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
const queryParams = new URLSearchParams({
|
||||
company_id: companyId.toString(),
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
const response = await authenticatedFetch(`v1/a76/units-of-measure/american/?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
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}`
|
||||
};
|
||||
return { error: 'Failed to load', american_units: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
items: data.items,
|
||||
total: data.total,
|
||||
page: data.page,
|
||||
pageSize: data.page_size,
|
||||
pages: data.pages
|
||||
};
|
||||
return { american_units: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error fetching American units:', error);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: 'Failed to connect to server'
|
||||
};
|
||||
console.error('Error loading American units:', error);
|
||||
return { error: 'Error loading', american_units: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<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 { createColumns } from '$lib/components/dashboard/general_catalogs/units_of_measure/american/columns';
|
||||
import DataTable from '$lib/components/dashboard/general_catalogs/units_of_measure/american/data-table.svelte';
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/units_of_measure/american/create-edit-dialog.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
@@ -14,10 +14,10 @@
|
||||
let loading = $state(false);
|
||||
|
||||
const columns = createColumns(() => {
|
||||
refreshData();
|
||||
handleSuccess();
|
||||
});
|
||||
|
||||
async function refreshData() {
|
||||
async function handleSuccess() {
|
||||
loading = true;
|
||||
await invalidateAll();
|
||||
loading = false;
|
||||
@@ -33,7 +33,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" onclick={refreshData} disabled={loading}>
|
||||
<Button variant="outline" size="icon" onclick={handleSuccess} disabled={loading}>
|
||||
<RefreshCw class="h-4 w-4 {loading ? 'animate-spin' : ''}" />
|
||||
</Button>
|
||||
<Button onclick={() => createDialogOpen = true}>
|
||||
@@ -44,18 +44,18 @@
|
||||
</div>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Content class="p-0">
|
||||
<Card.Content class="p-6">
|
||||
<DataTable
|
||||
data={data.items}
|
||||
{columns}
|
||||
pageCount={data.pages}
|
||||
totalItems={data.total}
|
||||
columns={columns}
|
||||
data={data.american_units?.items || []}
|
||||
pageCount={data.american_units?.pages || 0}
|
||||
totalItems={data.american_units?.total || 0}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={refreshData}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,56 +1,45 @@
|
||||
import { getServerApiUrl, getAuthTokens } from '$lib/server/api';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url }) => {
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
throw redirect(302, '/login');
|
||||
return { error: 'No authenticated', customs_units: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
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/customs?page=${page}&page_size=${pageSize}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
}
|
||||
});
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const parentData = await parent();
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
const queryParams = new URLSearchParams({
|
||||
company_id: companyId.toString(),
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
const response = await authenticatedFetch(`v1/a76/units-of-measure/customs/?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Error fetching Customs units: ${response.status} ${response.statusText}`);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: `Error: ${response.statusText}`
|
||||
};
|
||||
return { error: 'Failed to load', customs_units: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
items: data.items,
|
||||
total: data.total,
|
||||
page: data.page,
|
||||
pageSize: data.page_size,
|
||||
pages: data.pages
|
||||
};
|
||||
return { customs_units: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error fetching Customs units:', error);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: 'Error al cargar datos'
|
||||
};
|
||||
console.error('Error loading Customs units:', error);
|
||||
return { error: 'Error loading', customs_units: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,61 +1,61 @@
|
||||
<script lang="ts">
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { createColumns } from '$lib/components/dashboard/units_of_measure/customs/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/units_of_measure/customs/create-edit-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/units_of_measure/customs/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';
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { createColumns } from '$lib/components/dashboard/general_catalogs/units_of_measure/customs/columns';
|
||||
import DataTable from '$lib/components/dashboard/general_catalogs/units_of_measure/customs/data-table.svelte';
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/units_of_measure/customs/create-edit-dialog.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 { data }: { data: PageData } = $props();
|
||||
|
||||
let createDialogOpen = $state(false);
|
||||
let loading = $state(false);
|
||||
let createDialogOpen = $state(false);
|
||||
let loading = $state(false);
|
||||
|
||||
const columns = createColumns(() => {
|
||||
refreshData();
|
||||
});
|
||||
// Función unificada para refrescar datos
|
||||
async function handleSuccess() {
|
||||
loading = true;
|
||||
await invalidateAll();
|
||||
loading = false;
|
||||
}
|
||||
|
||||
async function refreshData() {
|
||||
loading = true;
|
||||
await invalidateAll();
|
||||
loading = false;
|
||||
}
|
||||
// Definimos las columnas pasando el callback de éxito
|
||||
const columns = createColumns(handleSuccess);
|
||||
</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 Aduanas MEX</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Catálogo de unidades de medida para aduanas mexicanas
|
||||
</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>
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold tracking-tight">Unidades de Medida Aduanas MEX</h2>
|
||||
<p class="text-muted-foreground">
|
||||
Catálogo de unidades de medida para aduanas mexicanas
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" onclick={handleSuccess} 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>
|
||||
<Card.Root>
|
||||
<Card.Content class="p-6">
|
||||
<DataTable
|
||||
{columns}
|
||||
data={data.customs_units?.items || []}
|
||||
pageCount={data.customs_units?.pages || 0}
|
||||
totalItems={data.customs_units?.total || 0}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={refreshData}
|
||||
/>
|
||||
</div>
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
@@ -1,56 +1,45 @@
|
||||
import { getServerApiUrl, getAuthTokens } from '$lib/server/api';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url }) => {
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
throw redirect(302, '/login');
|
||||
return { error: 'No authenticated', general_units: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
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?page=${page}&page_size=${pageSize}`;
|
||||
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${accessToken}`
|
||||
}
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const parentData = await parent();
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
const queryParams = new URLSearchParams({
|
||||
company_id: companyId.toString(),
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
const response = await authenticatedFetch(`v1/a76/units-of-measure/general/?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Error fetching General units: ${response.status} ${response.statusText}`);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: `Error: ${response.statusText}`
|
||||
};
|
||||
return { error: 'Failed to load', general_units: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
items: data.items,
|
||||
total: data.total,
|
||||
page: data.page,
|
||||
pageSize: data.page_size,
|
||||
pages: data.pages
|
||||
};
|
||||
return { general_units: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error fetching General units:', error);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: 'Error al cargar datos'
|
||||
};
|
||||
console.error('Error loading General units:', error);
|
||||
return { error: 'Error loading', general_units: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { createColumns } from '$lib/components/dashboard/units_of_measure/general/columns';
|
||||
import CreateEditDialog from '$lib/components/dashboard/units_of_measure/general/create-edit-dialog.svelte';
|
||||
import DataTable from '$lib/components/dashboard/units_of_measure/general/data-table.svelte';
|
||||
import { createColumns } from '$lib/components/dashboard/general_catalogs/units_of_measure/general/columns';
|
||||
import DataTable from '$lib/components/dashboard/general_catalogs/units_of_measure/general/data-table.svelte';
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/units_of_measure/general/create-edit-dialog.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
@@ -14,10 +14,10 @@
|
||||
let loading = $state(false);
|
||||
|
||||
const columns = createColumns(() => {
|
||||
refreshData();
|
||||
handleSuccess();
|
||||
});
|
||||
|
||||
async function refreshData() {
|
||||
async function handleSuccess() {
|
||||
loading = true;
|
||||
await invalidateAll();
|
||||
loading = false;
|
||||
@@ -33,7 +33,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" onclick={refreshData} disabled={loading}>
|
||||
<Button variant="outline" size="icon" onclick={handleSuccess} disabled={loading}>
|
||||
<RefreshCw class="h-4 w-4 {loading ? 'animate-spin' : ''}" />
|
||||
</Button>
|
||||
<Button onclick={() => createDialogOpen = true}>
|
||||
@@ -44,18 +44,18 @@
|
||||
</div>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Content class="p-0">
|
||||
<Card.Content class="p-6">
|
||||
<DataTable
|
||||
data={data.items}
|
||||
{columns}
|
||||
pageCount={data.pages}
|
||||
totalItems={data.total}
|
||||
columns={columns}
|
||||
data={data.general_units?.items || []}
|
||||
pageCount={data.general_units?.pages || 0}
|
||||
totalItems={data.general_units?.total || 0}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={refreshData}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,56 +1,45 @@
|
||||
import { getServerApiUrl, getAuthTokens } from '$lib/server/api';
|
||||
import { getAuthTokens, authenticatedFetch } from '$lib/server/api';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url }) => {
|
||||
export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => {
|
||||
await parent();
|
||||
const { accessToken } = getAuthTokens(cookies);
|
||||
|
||||
if (!accessToken) {
|
||||
throw redirect(302, '/login');
|
||||
return { error: 'No authenticated', oma_units: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
|
||||
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}`
|
||||
}
|
||||
const page = Number(url.searchParams.get('page')) || 1;
|
||||
const pageSize = Number(url.searchParams.get('pageSize')) || 50;
|
||||
const filters: Record<string, string> = {};
|
||||
const code = url.searchParams.get('code');
|
||||
const description = url.searchParams.get('description');
|
||||
|
||||
if (code) filters.code = code;
|
||||
if (description) filters.description = description;
|
||||
|
||||
const parentData = await parent();
|
||||
const cookieCompanyId = cookies.get('active_company_id');
|
||||
const companyId = cookieCompanyId
|
||||
? parseInt(cookieCompanyId)
|
||||
: parentData.companies?.[0]?.id;
|
||||
|
||||
const queryParams = new URLSearchParams({
|
||||
company_id: companyId.toString(),
|
||||
page: page.toString(),
|
||||
page_size: pageSize.toString(),
|
||||
...filters
|
||||
});
|
||||
const response = await authenticatedFetch(`v1/a76/units-of-measure/oma/?${queryParams.toString()}`, { method: 'GET' }, cookies, fetch);
|
||||
|
||||
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}`
|
||||
};
|
||||
return { error: 'Failed to load', oma_units: { items: [], total: 0, page, page_size: pageSize, pages: 0 } };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
items: data.items,
|
||||
total: data.total,
|
||||
page: data.page,
|
||||
pageSize: data.page_size,
|
||||
pages: data.pages
|
||||
};
|
||||
return { oma_units: await response.json() };
|
||||
} catch (error) {
|
||||
console.error('Error fetching OMA units:', error);
|
||||
return {
|
||||
items: [],
|
||||
total: 0,
|
||||
page,
|
||||
pageSize,
|
||||
pages: 0,
|
||||
error: 'Failed to connect to server'
|
||||
};
|
||||
console.error('Error loading OMA units:', error);
|
||||
return { error: 'Error loading', oma_units: { items: [], total: 0, page: 1, page_size: 50, pages: 0 } };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<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 { createColumns } from '$lib/components/dashboard/general_catalogs/units_of_measure/oma/columns';
|
||||
import DataTable from '$lib/components/dashboard/general_catalogs/units_of_measure/oma/data-table.svelte';
|
||||
import CreateEditDialog from '$lib/components/dashboard/general_catalogs/units_of_measure/oma/create-edit-dialog.svelte';
|
||||
import * as Card from '$lib/components/ui/card';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Plus, RefreshCw } from 'lucide-svelte';
|
||||
@@ -14,10 +14,10 @@
|
||||
let loading = $state(false);
|
||||
|
||||
const columns = createColumns(() => {
|
||||
refreshData();
|
||||
handleSuccess();
|
||||
});
|
||||
|
||||
async function refreshData() {
|
||||
async function handleSuccess() {
|
||||
loading = true;
|
||||
await invalidateAll();
|
||||
loading = false;
|
||||
@@ -33,7 +33,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Button variant="outline" size="icon" onclick={refreshData} disabled={loading}>
|
||||
<Button variant="outline" size="icon" onclick={handleSuccess} disabled={loading}>
|
||||
<RefreshCw class="h-4 w-4 {loading ? 'animate-spin' : ''}" />
|
||||
</Button>
|
||||
<Button onclick={() => createDialogOpen = true}>
|
||||
@@ -44,18 +44,18 @@
|
||||
</div>
|
||||
|
||||
<Card.Root>
|
||||
<Card.Content class="p-0">
|
||||
<Card.Content class="p-6">
|
||||
<DataTable
|
||||
data={data.items}
|
||||
{columns}
|
||||
pageCount={data.pages}
|
||||
totalItems={data.total}
|
||||
columns={columns}
|
||||
data={data.oma_units?.items || []}
|
||||
pageCount={data.oma_units?.pages || 0}
|
||||
totalItems={data.oma_units?.total || 0}
|
||||
/>
|
||||
</Card.Content>
|
||||
</Card.Root>
|
||||
|
||||
<CreateEditDialog
|
||||
bind:open={createDialogOpen}
|
||||
onSuccess={refreshData}
|
||||
onSuccess={handleSuccess}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user