342 lines
14 KiB
TypeScript
342 lines
14 KiB
TypeScript
|
|
import { AppBreadcrumb, AppContent, BackHistoryButton } from "@repo/rdx-ui/components";
|
||
|
|
import { Button, Card, CardContent, CardHeader, CardTitle } from "@repo/shadcn-ui/components";
|
||
|
|
import {
|
||
|
|
Banknote,
|
||
|
|
Building2,
|
||
|
|
EditIcon,
|
||
|
|
FileText,
|
||
|
|
Globe,
|
||
|
|
Languages,
|
||
|
|
Mail,
|
||
|
|
MapPin,
|
||
|
|
MoreVertical,
|
||
|
|
Phone,
|
||
|
|
Smartphone,
|
||
|
|
User,
|
||
|
|
} from "lucide-react";
|
||
|
|
import { useNavigate } from "react-router-dom";
|
||
|
|
|
||
|
|
import { useUrlParamId } from "@erp/core/hooks";
|
||
|
|
import { Badge } from "@repo/shadcn-ui/components";
|
||
|
|
import { CustomerEditorSkeleton, ErrorAlert } from "../../components";
|
||
|
|
import { useCustomerQuery } from "../../hooks";
|
||
|
|
import { useTranslation } from "../../i18n";
|
||
|
|
|
||
|
|
export const CustomerViewPage = () => {
|
||
|
|
const customerId = useUrlParamId();
|
||
|
|
const { t } = useTranslation();
|
||
|
|
const navigate = useNavigate();
|
||
|
|
|
||
|
|
// 1) Estado de carga del cliente (query)
|
||
|
|
const {
|
||
|
|
data: customer,
|
||
|
|
isLoading: isLoadingCustomer,
|
||
|
|
isError: isLoadError,
|
||
|
|
error: loadError,
|
||
|
|
} = useCustomerQuery(customerId, { enabled: !!customerId });
|
||
|
|
|
||
|
|
if (isLoadingCustomer) {
|
||
|
|
return <CustomerEditorSkeleton />;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (isLoadError) {
|
||
|
|
return (
|
||
|
|
<>
|
||
|
|
<AppBreadcrumb />
|
||
|
|
<AppContent>
|
||
|
|
<ErrorAlert
|
||
|
|
title={t("pages.update.loadErrorTitle", "No se pudo cargar el cliente")}
|
||
|
|
message={
|
||
|
|
(loadError as Error)?.message ??
|
||
|
|
t("pages.update.loadErrorMsg", "Inténtalo de nuevo más tarde.")
|
||
|
|
}
|
||
|
|
/>
|
||
|
|
|
||
|
|
<div className='flex items-center justify-end'>
|
||
|
|
<BackHistoryButton />
|
||
|
|
</div>
|
||
|
|
</AppContent>
|
||
|
|
</>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<>
|
||
|
|
<AppBreadcrumb />
|
||
|
|
<AppContent>
|
||
|
|
<div className='space-y-6 max-w-4xl'>
|
||
|
|
{/* Header */}
|
||
|
|
<div className='flex items-start justify-between'>
|
||
|
|
<div className='flex items-start gap-4'>
|
||
|
|
<div className='flex h-16 w-16 items-center justify-center rounded-lg bg-primary/10'>
|
||
|
|
{customer?.is_company ? (
|
||
|
|
<Building2 className='h-8 w-8 text-primary' />
|
||
|
|
) : (
|
||
|
|
<User className='h-8 w-8 text-primary' />
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<h1 className='text-3xl font-bold text-foreground'>{customer?.name}</h1>
|
||
|
|
<div className='mt-2 flex items-center gap-3'>
|
||
|
|
<Badge variant='secondary' className='font-mono'>
|
||
|
|
{customer?.reference}
|
||
|
|
</Badge>
|
||
|
|
<Badge variant='outline'>{customer?.is_company ? "Empresa" : "Persona"}</Badge>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div className='flex gap-2'>
|
||
|
|
<Button variant='outline' size='icon' onClick={() => navigate("/customers/list")}>
|
||
|
|
<MoreVertical className='h-4 w-4' />
|
||
|
|
</Button>
|
||
|
|
<Button>
|
||
|
|
<EditIcon className='mr-2 h-4 w-4' />
|
||
|
|
Editar
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Main Content Grid */}
|
||
|
|
<div className='grid gap-6 md:grid-cols-2'>
|
||
|
|
{/* Información Básica */}
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle className='flex items-center gap-2 text-lg'>
|
||
|
|
<FileText className='h-5 w-5 text-primary' />
|
||
|
|
Información Básica
|
||
|
|
</CardTitle>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent className='space-y-4'>
|
||
|
|
<div>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>Nombre</dt>
|
||
|
|
<dd className='mt-1 text-base text-foreground'>{customer?.name}</dd>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>Referencia</dt>
|
||
|
|
<dd className='mt-1 font-mono text-base text-foreground'>
|
||
|
|
{customer?.reference}
|
||
|
|
</dd>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>Registro Legal</dt>
|
||
|
|
<dd className='mt-1 text-base text-foreground'>{customer?.legalRecord}</dd>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>
|
||
|
|
Impuestos por Defecto
|
||
|
|
</dt>
|
||
|
|
<dd className='mt-1'>
|
||
|
|
<Badge className='bg-blue-600 hover:bg-blue-700'>{customer?.defaultTax}</Badge>
|
||
|
|
</dd>
|
||
|
|
</div>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
|
||
|
|
{/* Dirección */}
|
||
|
|
<Card>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle className='flex items-center gap-2 text-lg'>
|
||
|
|
<MapPin className='h-5 w-5 text-primary' />
|
||
|
|
Dirección
|
||
|
|
</CardTitle>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent className='space-y-4'>
|
||
|
|
<div>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>Calle</dt>
|
||
|
|
<dd className='mt-1 text-base text-foreground'>
|
||
|
|
{customer?.street1}
|
||
|
|
{customer?.street2 && (
|
||
|
|
<>
|
||
|
|
<br />
|
||
|
|
{customer?.street2}
|
||
|
|
</>
|
||
|
|
)}
|
||
|
|
</dd>
|
||
|
|
</div>
|
||
|
|
<div className='grid grid-cols-2 gap-4'>
|
||
|
|
<div>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>Ciudad</dt>
|
||
|
|
<dd className='mt-1 text-base text-foreground'>{customer?.city}</dd>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>Código Postal</dt>
|
||
|
|
<dd className='mt-1 text-base text-foreground'>{customer?.postal_code}</dd>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div className='grid grid-cols-2 gap-4'>
|
||
|
|
<div>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>Provincia</dt>
|
||
|
|
<dd className='mt-1 text-base text-foreground'>{customer?.province}</dd>
|
||
|
|
</div>
|
||
|
|
<div>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>País</dt>
|
||
|
|
<dd className='mt-1 text-base text-foreground'>{customer?.country}</dd>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
|
||
|
|
{/* Información de Contacto */}
|
||
|
|
<Card className='md:col-span-2'>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle className='flex items-center gap-2 text-lg'>
|
||
|
|
<Mail className='h-5 w-5 text-primary' />
|
||
|
|
Información de Contacto
|
||
|
|
</CardTitle>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
<div className='grid gap-6 md:grid-cols-2'>
|
||
|
|
{/* Contacto Principal */}
|
||
|
|
<div className='space-y-4'>
|
||
|
|
<h3 className='font-semibold text-foreground'>Contacto Principal</h3>
|
||
|
|
{customer?.email_primary && (
|
||
|
|
<div className='flex items-start gap-3'>
|
||
|
|
<Mail className='mt-0.5 h-4 w-4 text-muted-foreground' />
|
||
|
|
<div className='flex-1'>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>Email</dt>
|
||
|
|
<dd className='mt-1 text-base text-foreground'>
|
||
|
|
{customer?.email_primary}
|
||
|
|
</dd>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
{customer?.mobile_primary && (
|
||
|
|
<div className='flex items-start gap-3'>
|
||
|
|
<Smartphone className='mt-0.5 h-4 w-4 text-muted-foreground' />
|
||
|
|
<div className='flex-1'>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>Móvil</dt>
|
||
|
|
<dd className='mt-1 text-base text-foreground'>
|
||
|
|
{customer?.mobile_primary}
|
||
|
|
</dd>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
{customer?.phone_primary && (
|
||
|
|
<div className='flex items-start gap-3'>
|
||
|
|
<Phone className='mt-0.5 h-4 w-4 text-muted-foreground' />
|
||
|
|
<div className='flex-1'>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>Teléfono</dt>
|
||
|
|
<dd className='mt-1 text-base text-foreground'>
|
||
|
|
{customer?.phone_primary}
|
||
|
|
</dd>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Contacto Secundario */}
|
||
|
|
<div className='space-y-4'>
|
||
|
|
<h3 className='font-semibold text-foreground'>Contacto Secundario</h3>
|
||
|
|
{customer?.email_secondary && (
|
||
|
|
<div className='flex items-start gap-3'>
|
||
|
|
<Mail className='mt-0.5 h-4 w-4 text-muted-foreground' />
|
||
|
|
<div className='flex-1'>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>Email</dt>
|
||
|
|
<dd className='mt-1 text-base text-foreground'>
|
||
|
|
{customer?.email_secondary}
|
||
|
|
</dd>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
{customer?.mobile_secondary && (
|
||
|
|
<div className='flex items-start gap-3'>
|
||
|
|
<Smartphone className='mt-0.5 h-4 w-4 text-muted-foreground' />
|
||
|
|
<div className='flex-1'>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>Móvil</dt>
|
||
|
|
<dd className='mt-1 text-base text-foreground'>
|
||
|
|
{customer?.mobile_secondary}
|
||
|
|
</dd>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
{customer?.phone_secondary && (
|
||
|
|
<div className='flex items-start gap-3'>
|
||
|
|
<Phone className='mt-0.5 h-4 w-4 text-muted-foreground' />
|
||
|
|
<div className='flex-1'>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>Teléfono</dt>
|
||
|
|
<dd className='mt-1 text-base text-foreground'>
|
||
|
|
{customer?.phone_secondary}
|
||
|
|
</dd>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* Otros Contactos */}
|
||
|
|
{(customer?.website || customer?.fax) && (
|
||
|
|
<div className='space-y-4 md:col-span-2'>
|
||
|
|
<h3 className='font-semibold text-foreground'>Otros</h3>
|
||
|
|
<div className='grid gap-4 md:grid-cols-2'>
|
||
|
|
{customer?.website && (
|
||
|
|
<div className='flex items-start gap-3'>
|
||
|
|
<Globe className='mt-0.5 h-4 w-4 text-muted-foreground' />
|
||
|
|
<div className='flex-1'>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>
|
||
|
|
Sitio Web
|
||
|
|
</dt>
|
||
|
|
<dd className='mt-1 text-base text-primary hover:underline'>
|
||
|
|
<a
|
||
|
|
href={customer?.website}
|
||
|
|
target='_blank'
|
||
|
|
rel='noopener noreferrer'
|
||
|
|
>
|
||
|
|
{customer?.website}
|
||
|
|
</a>
|
||
|
|
</dd>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
{customer?.fax && (
|
||
|
|
<div className='flex items-start gap-3'>
|
||
|
|
<Phone className='mt-0.5 h-4 w-4 text-muted-foreground' />
|
||
|
|
<div className='flex-1'>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>Fax</dt>
|
||
|
|
<dd className='mt-1 text-base text-foreground'>{customer?.fax}</dd>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
|
||
|
|
{/* Preferencias */}
|
||
|
|
<Card className='md:col-span-2'>
|
||
|
|
<CardHeader>
|
||
|
|
<CardTitle className='flex items-center gap-2 text-lg'>
|
||
|
|
<Languages className='h-5 w-5 text-primary' />
|
||
|
|
Preferencias
|
||
|
|
</CardTitle>
|
||
|
|
</CardHeader>
|
||
|
|
<CardContent>
|
||
|
|
<div className='grid gap-6 md:grid-cols-2'>
|
||
|
|
<div className='flex items-start gap-3'>
|
||
|
|
<Languages className='mt-0.5 h-4 w-4 text-muted-foreground' />
|
||
|
|
<div className='flex-1'>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>
|
||
|
|
Idioma Preferido
|
||
|
|
</dt>
|
||
|
|
<dd className='mt-1 text-base text-foreground'>{customer?.language_code}</dd>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div className='flex items-start gap-3'>
|
||
|
|
<Banknote className='mt-0.5 h-4 w-4 text-muted-foreground' />
|
||
|
|
<div className='flex-1'>
|
||
|
|
<dt className='text-sm font-medium text-muted-foreground'>
|
||
|
|
Moneda Preferida
|
||
|
|
</dt>
|
||
|
|
<dd className='mt-1 text-base text-foreground'>{customer?.currency_code}</dd>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</CardContent>
|
||
|
|
</Card>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</AppContent>
|
||
|
|
</>
|
||
|
|
);
|
||
|
|
};
|