315 lines
11 KiB
TypeScript
315 lines
11 KiB
TypeScript
import { useState } from 'react';
|
|
import { useNavigate, useParams } from 'react-router-dom';
|
|
import {
|
|
ArrowLeft,
|
|
Edit,
|
|
Trash2,
|
|
Mail,
|
|
Phone,
|
|
Globe,
|
|
Building2,
|
|
MapPin,
|
|
FileText,
|
|
} from 'lucide-react';
|
|
import { Button } from '@components/atoms/Button';
|
|
import { Spinner } from '@components/atoms/Spinner';
|
|
import { Card, CardHeader, CardTitle, CardContent } from '@components/molecules/Card';
|
|
import { Breadcrumbs } from '@components/organisms/Breadcrumbs';
|
|
import { ConfirmModal } from '@components/organisms/Modal';
|
|
import { useToast } from '@components/organisms/Toast';
|
|
import { ErrorEmptyState } from '@components/templates/EmptyState';
|
|
import { useCompany, useCompanyChildren } from '@features/companies/hooks';
|
|
import { companiesApi } from '@features/companies/api';
|
|
import { formatDate } from '@utils/formatters';
|
|
|
|
export function CompanyDetailPage() {
|
|
const { id } = useParams<{ id: string }>();
|
|
const navigate = useNavigate();
|
|
const { showToast } = useToast();
|
|
const { company, isLoading, error, refresh } = useCompany(id);
|
|
const { children } = useCompanyChildren(id);
|
|
|
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
|
|
const handleDelete = async () => {
|
|
if (!id) return;
|
|
setIsProcessing(true);
|
|
try {
|
|
await companiesApi.delete(id);
|
|
showToast({
|
|
type: 'success',
|
|
title: 'Empresa eliminada',
|
|
message: 'La empresa ha sido eliminada exitosamente.',
|
|
});
|
|
navigate('/companies');
|
|
} catch {
|
|
showToast({
|
|
type: 'error',
|
|
title: 'Error',
|
|
message: 'No se pudo eliminar la empresa.',
|
|
});
|
|
} finally {
|
|
setIsProcessing(false);
|
|
setShowDeleteModal(false);
|
|
}
|
|
};
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex h-96 items-center justify-center">
|
|
<Spinner size="lg" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error || !company) {
|
|
return (
|
|
<div className="p-6">
|
|
<ErrorEmptyState
|
|
title="Empresa no encontrada"
|
|
description="No se pudo cargar la información de la empresa."
|
|
onRetry={refresh}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="space-y-6 p-6">
|
|
<Breadcrumbs
|
|
items={[
|
|
{ label: 'Empresas', href: '/companies' },
|
|
{ label: company.name },
|
|
]}
|
|
/>
|
|
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-4">
|
|
<Button variant="ghost" size="sm" onClick={() => navigate('/companies')}>
|
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
Volver
|
|
</Button>
|
|
<div>
|
|
<h1 className="text-2xl font-bold text-gray-900">Detalle de empresa</h1>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
<Button variant="outline" onClick={() => navigate(`/companies/${id}/edit`)}>
|
|
<Edit className="mr-2 h-4 w-4" />
|
|
Editar
|
|
</Button>
|
|
<Button variant="danger" onClick={() => setShowDeleteModal(true)}>
|
|
<Trash2 className="mr-2 h-4 w-4" />
|
|
Eliminar
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid gap-6 lg:grid-cols-3">
|
|
{/* Company info card */}
|
|
<div className="lg:col-span-1">
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
<div className="flex flex-col items-center text-center">
|
|
<div className="flex h-20 w-20 items-center justify-center rounded-xl bg-primary-100">
|
|
<Building2 className="h-10 w-10 text-primary-600" />
|
|
</div>
|
|
<h2 className="mt-4 text-xl font-semibold text-gray-900">
|
|
{company.name}
|
|
</h2>
|
|
{company.legalName && (
|
|
<p className="text-sm text-gray-500">{company.legalName}</p>
|
|
)}
|
|
{company.taxId && (
|
|
<span className="mt-2 inline-flex rounded-full bg-gray-100 px-3 py-1 text-sm font-medium text-gray-700">
|
|
{company.taxId}
|
|
</span>
|
|
)}
|
|
|
|
<div className="mt-6 w-full space-y-3 text-left">
|
|
{company.settings?.email && (
|
|
<div className="flex items-center gap-3 text-sm">
|
|
<Mail className="h-4 w-4 text-gray-400" />
|
|
<a href={`mailto:${company.settings.email}`} className="text-primary-600 hover:underline">
|
|
{company.settings.email}
|
|
</a>
|
|
</div>
|
|
)}
|
|
{company.settings?.phone && (
|
|
<div className="flex items-center gap-3 text-sm">
|
|
<Phone className="h-4 w-4 text-gray-400" />
|
|
<span>{company.settings.phone}</span>
|
|
</div>
|
|
)}
|
|
{company.settings?.website && (
|
|
<div className="flex items-center gap-3 text-sm">
|
|
<Globe className="h-4 w-4 text-gray-400" />
|
|
<a href={company.settings.website} target="_blank" rel="noopener noreferrer" className="text-primary-600 hover:underline">
|
|
{company.settings.website}
|
|
</a>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Details */}
|
|
<div className="space-y-6 lg:col-span-2">
|
|
{/* Address */}
|
|
{(company.settings?.address || company.settings?.city) && (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<MapPin className="h-5 w-5" />
|
|
Dirección
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="space-y-1">
|
|
{company.settings?.address && (
|
|
<p className="text-gray-900">{company.settings.address}</p>
|
|
)}
|
|
<p className="text-gray-600">
|
|
{[
|
|
company.settings?.city,
|
|
company.settings?.state,
|
|
company.settings?.zipCode,
|
|
]
|
|
.filter(Boolean)
|
|
.join(', ')}
|
|
</p>
|
|
{company.settings?.country && (
|
|
<p className="text-gray-600">{company.settings.country}</p>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{/* Fiscal Info */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<FileText className="h-5 w-5" />
|
|
Información fiscal
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<dl className="grid gap-4 sm:grid-cols-2">
|
|
<div>
|
|
<dt className="text-sm text-gray-500">RFC / Tax ID</dt>
|
|
<dd className="font-medium text-gray-900">{company.taxId || 'No especificado'}</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-sm text-gray-500">Moneda</dt>
|
|
<dd className="font-medium text-gray-900">{company.currencyCode || 'MXN'}</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-sm text-gray-500">Régimen fiscal</dt>
|
|
<dd className="font-medium text-gray-900">
|
|
{company.settings?.taxRegime || 'No especificado'}
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-sm text-gray-500">Posición fiscal</dt>
|
|
<dd className="font-medium text-gray-900">
|
|
{company.settings?.fiscalPosition || 'No especificado'}
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Hierarchy */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Building2 className="h-5 w-5" />
|
|
Jerarquía corporativa
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<dl className="grid gap-4 sm:grid-cols-2">
|
|
<div>
|
|
<dt className="text-sm text-gray-500">Empresa matriz</dt>
|
|
<dd className="font-medium text-gray-900">
|
|
{company.parentCompanyName || 'Ninguna (empresa raíz)'}
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-sm text-gray-500">Subsidiarias</dt>
|
|
<dd className="font-medium text-gray-900">
|
|
{children.length > 0 ? (
|
|
<ul className="list-inside list-disc">
|
|
{children.map((child) => (
|
|
<li key={child.id}>
|
|
<button
|
|
onClick={() => navigate(`/companies/${child.id}`)}
|
|
className="text-primary-600 hover:underline"
|
|
>
|
|
{child.name}
|
|
</button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
) : (
|
|
'Ninguna'
|
|
)}
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* System Info */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Información del sistema</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<dl className="grid gap-4 sm:grid-cols-2">
|
|
<div>
|
|
<dt className="text-sm text-gray-500">ID</dt>
|
|
<dd className="font-mono text-sm text-gray-900">{company.id}</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-sm text-gray-500">Tenant ID</dt>
|
|
<dd className="font-mono text-sm text-gray-900">{company.tenantId}</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-sm text-gray-500">Creado</dt>
|
|
<dd className="text-gray-900">{formatDate(company.createdAt, 'full')}</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-sm text-gray-500">Actualizado</dt>
|
|
<dd className="text-gray-900">
|
|
{company.updatedAt ? formatDate(company.updatedAt, 'full') : '-'}
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Delete Modal */}
|
|
<ConfirmModal
|
|
isOpen={showDeleteModal}
|
|
onClose={() => setShowDeleteModal(false)}
|
|
onConfirm={handleDelete}
|
|
title="Eliminar empresa"
|
|
message={`¿Estás seguro de que deseas eliminar "${company.name}"? Esta acción no se puede deshacer.`}
|
|
variant="danger"
|
|
confirmText="Eliminar"
|
|
isLoading={isProcessing}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default CompanyDetailPage;
|