erp-core-frontend-v2/src/pages/companies/CompanyDetailPage.tsx

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;