diff --git a/modules/core/src/web/components/empty-cell-value.tsx b/modules/core/src/web/components/empty-cell-value.tsx index 181481dd..d55aac71 100644 --- a/modules/core/src/web/components/empty-cell-value.tsx +++ b/modules/core/src/web/components/empty-cell-value.tsx @@ -5,15 +5,6 @@ type EmptyCellValueProps = { value?: string; }; -export const EmptyCellValue = ({ - className, - value = "\u2014", -}: EmptyCellValueProps) => ( - - {value} - +export const EmptyCellValue = ({ className, value = "\u2014" }: EmptyCellValueProps) => ( + {value} ); - diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/blocks/index.ts b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/index.ts new file mode 100644 index 00000000..e36cd5d3 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/index.ts @@ -0,0 +1,4 @@ +export * from "./proforma-detail-header-actions"; +export * from "./proforma-detail-preview-card"; +export * from "./proforma-detail-sidebar"; +export * from "./proforma-detail-summary-card"; diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-header-actions.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-header-actions.tsx new file mode 100644 index 00000000..17504cf7 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-header-actions.tsx @@ -0,0 +1,57 @@ +import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@repo/shadcn-ui/components"; +import { DownloadIcon, MoreHorizontalIcon, PencilIcon, RefreshCwIcon } from "lucide-react"; + +type ProformaDetailHeaderActionsProps = { + primaryAction: { + label: string; + onClick: () => void; + }; + isPrimaryLoading: boolean; + isArchived: boolean; + onDownload: () => void; + onDuplicate: () => void; + onArchiveToggle: () => void; +}; + +export const ProformaDetailHeaderActions = ({ + primaryAction, + isPrimaryLoading, + isArchived, + onDownload, + onDuplicate, + onArchiveToggle, +}: ProformaDetailHeaderActionsProps) => { + return ( +
+ + + + + + + + + } + /> + + Duplicar proforma + + {isArchived ? "Desarchivar" : "Archivar"} + + + +
+ ); +}; diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-preview-card.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-preview-card.tsx new file mode 100644 index 00000000..68f90d98 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-preview-card.tsx @@ -0,0 +1,39 @@ +import { PDFViewer } from "@repo/rdx-ui/components"; +import { Card, CardContent } from "@repo/shadcn-ui/components"; + +type ProformaDetailPreviewCardProps = { + src: string | null; + isLoading: boolean; + error: string | null; + onOpen: () => void; + onDownload: () => void; + onPrint: () => void; +}; + +export const ProformaDetailPreviewCard = ({ + src, + isLoading, + error, + onOpen, + onDownload, + onPrint, +}: ProformaDetailPreviewCardProps) => { + return ( + + + + + + ); +}; diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-sidebar.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-sidebar.tsx new file mode 100644 index 00000000..c85c52fe --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-sidebar.tsx @@ -0,0 +1,28 @@ +import type { Proforma } from "../../../shared"; +import { ProformaDetailStatusInspectorCard } from "../components"; + +import { ProformaDetailSummaryCard } from "./proforma-detail-summary-card"; + +type ProformaDetailSidebarProps = { + proforma: Proforma; + paymentMethodLabel: string | null; + paymentTermLabel: string | null; + onEdit: () => void; +}; + +export const ProformaDetailSidebar = ({ + proforma, + paymentMethodLabel, + paymentTermLabel, +}: ProformaDetailSidebarProps) => { + return ( +
+ + +
+ ); +}; diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-summary-card.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-summary-card.tsx new file mode 100644 index 00000000..dd8cc9a3 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/detail/ui/blocks/proforma-detail-summary-card.tsx @@ -0,0 +1,159 @@ +import { FieldValueList, FormSectionCard, FormSectionGrid } from "@repo/rdx-ui/components"; +import { Separator } from "@repo/shadcn-ui/components"; +import { LandmarkIcon } from "lucide-react"; + +import { type Proforma } from "../../../shared"; +import { formatProformaDate, formatProformaMoney } from "../components/proforma-detail-formatters"; + +type ProformaDetailSummaryCardProps = { + proforma: Proforma; + paymentMethodLabel: string | null; + paymentTermLabel: string | null; +}; + +const SectionHeading = ({ title, description }: { title: string; description?: string }) => { + return ( +
+

{title}

+ {description ?

{description}

: null} +
+ ); +}; + +export const ProformaDetailSummaryCard = ({ + proforma, + paymentMethodLabel, + paymentTermLabel, +}: ProformaDetailSummaryCardProps) => { + const billingItems = [ + { + label: "Método de pago", + value: paymentMethodLabel ?? "\u2014", + }, + { + label: "Plazo", + value: paymentTermLabel ?? "\u2014", + }, + { + label: "Moneda", + value: proforma.currencyCode, + }, + ]; + + const datesItems = [ + { + label: "Fecha proforma", + value: formatProformaDate(proforma.proformaDate), + }, + { + label: "Fecha operación", + value: formatProformaDate(proforma.operationDate), + }, + { + label: "Serie", + value: proforma.series ?? "\u2014", + }, + { + label: "Referencia", + value: proforma.reference ?? "\u2014", + }, + ]; + + const totalsItems = [ + { + label: "Subtotal", + value: formatProformaMoney(proforma.subtotalAmount, proforma.currencyCode, proforma.languageCode), + }, + { + label: "Descuentos", + value: `-${formatProformaMoney(proforma.totalDiscountAmount, proforma.currencyCode, proforma.languageCode)}`, + valueClassName: "text-red-600", + }, + { + label: "IVA", + value: formatProformaMoney(proforma.ivaAmount, proforma.currencyCode, proforma.languageCode), + }, + { + label: "Total", + value: formatProformaMoney(proforma.totalAmount, proforma.currencyCode, proforma.languageCode), + valueClassName: "text-lg font-semibold text-foreground", + }, + ]; + + return ( + } + title="Resumen de proforma" + > + +
+ + +
+ +
+ +
+ +
+ + +
+ +
+ +
+ +
+ +
+
+

Descripción

+

{proforma.description ?? "Sin descripción."}

+
+ +
+

Observaciones

+

{proforma.notes ?? "Sin notas adicionales."}

+
+
+
+ +
+ +
+ +
+ + +
+
+
+ ); +}; diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/index.ts b/modules/customer-invoices/src/web/proformas/detail/ui/components/index.ts new file mode 100644 index 00000000..a6873af8 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/index.ts @@ -0,0 +1,9 @@ +export * from "./proforma-detail-archived-badge"; +export * from "./proforma-detail-billing-inspector-card"; +export * from "./proforma-detail-dates-inspector-card"; +export * from "./proforma-detail-header-meta"; +export * from "./proforma-detail-inspector-card"; +export * from "./proforma-detail-notes-inspector-card"; +export * from "./proforma-detail-skeleton"; +export * from "./proforma-detail-status-inspector-card"; +export * from "./proforma-detail-totals-inspector-card"; diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-archived-badge.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-archived-badge.tsx new file mode 100644 index 00000000..e49d2dc1 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-archived-badge.tsx @@ -0,0 +1,33 @@ +import { Tooltip, TooltipContent, TooltipTrigger } from "@repo/shadcn-ui/components"; +import { cn } from "@repo/shadcn-ui/lib/utils"; +import { ArchiveIcon } from "lucide-react"; + +type ProformaDetailArchivedBadgeProps = { + className?: string; +}; + +export const ProformaDetailArchivedBadge = ({ + className, +}: ProformaDetailArchivedBadgeProps) => { + return ( + + svg]:pointer-events-none [&>svg]:size-3.5 [&>svg]:shrink-0", + className + )} + > + + ); +}; diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-billing-inspector-card.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-billing-inspector-card.tsx new file mode 100644 index 00000000..a3e44268 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-billing-inspector-card.tsx @@ -0,0 +1,40 @@ +import { FieldValueList } from "@repo/rdx-ui/components"; + +import type { Proforma } from "../../../shared"; + +import { ProformaDetailInspectorCard } from "./proforma-detail-inspector-card"; + +type ProformaDetailBillingInspectorCardProps = { + proforma: Proforma; + paymentMethodLabel: string | null; + paymentTermLabel: string | null; + onEdit: () => void; +}; + +export const ProformaDetailBillingInspectorCard = ({ + proforma, + paymentMethodLabel, + paymentTermLabel, + onEdit, +}: ProformaDetailBillingInspectorCardProps) => { + const items = [ + { + label: "Método de pago", + value: paymentMethodLabel ?? "\u2014", + }, + { + label: "Plazo", + value: paymentTermLabel ?? "\u2014", + }, + { + label: "Moneda", + value: proforma.currencyCode, + }, + ]; + + return ( + + + + ); +}; diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-dates-inspector-card.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-dates-inspector-card.tsx new file mode 100644 index 00000000..e065536d --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-dates-inspector-card.tsx @@ -0,0 +1,41 @@ +import { FieldValueList } from "@repo/rdx-ui/components"; + +import type { Proforma } from "../../../shared"; + +import { formatProformaDate } from "./proforma-detail-formatters"; +import { ProformaDetailInspectorCard } from "./proforma-detail-inspector-card"; + +type ProformaDetailDatesInspectorCardProps = { + proforma: Proforma; + onEdit: () => void; +}; + +export const ProformaDetailDatesInspectorCard = ({ + proforma, + onEdit, +}: ProformaDetailDatesInspectorCardProps) => { + const items = [ + { + label: "Fecha proforma", + value: formatProformaDate(proforma.proformaDate), + }, + { + label: "Fecha operación", + value: formatProformaDate(proforma.operationDate), + }, + { + label: "Serie de la factura", + value: proforma.series ?? "\u2014", + }, + { + label: "Referencia interna", + value: proforma.reference ?? "\u2014", + }, + ]; + + return ( + + + + ); +}; diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-formatters.ts b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-formatters.ts new file mode 100644 index 00000000..a280be65 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-formatters.ts @@ -0,0 +1,17 @@ +import { DateHelper, MoneyHelper } from "@repo/rdx-utils"; + +export const formatProformaDate = (value: string | null | undefined) => { + if (!value) { + return "\u2014"; + } + + return DateHelper.format(value); +}; + +export const formatProformaMoney = ( + amount: number, + currencyCode: string, + languageCode?: string | null +) => { + return MoneyHelper.formatCurrency(amount, 2, currencyCode, languageCode ?? undefined); +}; diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-header-meta.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-header-meta.tsx new file mode 100644 index 00000000..d270bcb6 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-header-meta.tsx @@ -0,0 +1,31 @@ +import { useMemo } from "react"; + +import type { Proforma } from "../../../shared"; + +import { formatProformaDate, formatProformaMoney } from "./proforma-detail-formatters"; + +type ProformaDetailHeaderMetaProps = { + proforma: Proforma; +}; + +export const ProformaDetailHeaderMeta = ({ proforma }: ProformaDetailHeaderMetaProps) => { + const items = useMemo( + () => [ + proforma.recipient.name ?? "Cliente sin nombre", + formatProformaDate(proforma.proformaDate), + `Total ${formatProformaMoney(proforma.totalAmount, proforma.currencyCode, proforma.languageCode)}`, + ], + [proforma] + ); + + return ( +
+ {items.map((item, index) => ( +
+ {index > 0 ? | : null} + {item} +
+ ))} +
+ ); +}; diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-inspector-card.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-inspector-card.tsx new file mode 100644 index 00000000..ec455f69 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-inspector-card.tsx @@ -0,0 +1,29 @@ +import { Button, Card, CardContent, CardHeader, CardTitle } from "@repo/shadcn-ui/components"; +import { SquarePenIcon } from "lucide-react"; +import type { ReactNode } from "react"; + +type ProformaDetailInspectorCardProps = { + title: string; + onEdit?: () => void; + children: ReactNode; +}; + +export const ProformaDetailInspectorCard = ({ + title, + onEdit, + children, +}: ProformaDetailInspectorCardProps) => { + return ( + + + {title} + {onEdit ? ( + + ) : null} + + {children} + + ); +}; diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-notes-inspector-card.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-notes-inspector-card.tsx new file mode 100644 index 00000000..2ec30d06 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-notes-inspector-card.tsx @@ -0,0 +1,31 @@ +import { Separator } from "@repo/shadcn-ui/components"; + +import type { Proforma } from "../../../shared"; + +import { ProformaDetailInspectorCard } from "./proforma-detail-inspector-card"; + +type ProformaDetailNotesInspectorCardProps = { + proforma: Proforma; + onEdit: () => void; +}; + +export const ProformaDetailNotesInspectorCard = ({ + proforma, + onEdit, +}: ProformaDetailNotesInspectorCardProps) => { + return ( + +
+
+

Descripción

+

{proforma.description ?? "Sin descripción."}

+
+ +
+

Observaciones

+

{proforma.notes ?? "Sin notas adicionales."}

+
+
+
+ ); +}; diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-skeleton.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-skeleton.tsx new file mode 100644 index 00000000..7dbc714d --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-skeleton.tsx @@ -0,0 +1,32 @@ +import { Card, CardContent, Skeleton } from "@repo/shadcn-ui/components"; + +export const ProformaDetailSkeleton = () => { + return ( +
+ + + + + + + + +
+ + + + + + + +
+ + + + + +
+
+
+ ); +}; diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-status-inspector-card.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-status-inspector-card.tsx new file mode 100644 index 00000000..ec08d804 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-status-inspector-card.tsx @@ -0,0 +1,76 @@ +import { Alert, AlertDescription, AlertTitle } from "@repo/shadcn-ui/components"; +import { cn } from "@repo/shadcn-ui/lib/utils"; +import { CheckCircle2Icon } from "lucide-react"; + +import type { Proforma } from "../../../shared"; + +const getStatusTone = (status: Proforma["status"]) => { + switch (status) { + case "approved": + return "border-emerald-200 bg-emerald-50 text-emerald-900"; + case "sent": + return "border-amber-200 bg-amber-50 text-amber-900"; + case "issued": + return "border-sky-200 bg-sky-50 text-sky-900"; + case "rejected": + return "border-red-200 bg-red-50 text-red-900"; + default: + return "border-slate-200 bg-slate-50 text-slate-900"; + } +}; + +const getStatusSummary = (proforma: Proforma) => { + if (proforma.isArchived) { + return { + title: "Proforma archivada", + description: "Está fuera del flujo principal y puedes recuperarla cuando lo necesites.", + }; + } + + switch (proforma.status) { + case "approved": + return { + title: "Lista para convertirse en factura", + description: "La proforma ya está aprobada por el cliente y puede emitirse como factura.", + }; + case "sent": + return { + title: "Pendiente de respuesta del cliente", + description: + "Se ha enviado al cliente y está a la espera de aceptación o rechazo por parte del cliente.", + }; + case "issued": + return { + title: "Convertida en factura", + description: "La proforma ya ha sido emitida y forma parte del flujo fiscal.", + }; + case "rejected": + return { + title: "Requiere revisión", + description: "El cliente la ha rechazado y conviene revisar condiciones o importes.", + }; + default: + return { + title: "Pendiente de revisión", + description: "Sigue en borrador y todavía puede modificarse antes de enviarla al cliente.", + }; + } +}; + +type ProformaDetailStatusInspectorCardProps = { + proforma: Proforma; +}; + +export const ProformaDetailStatusInspectorCard = ({ + proforma, +}: ProformaDetailStatusInspectorCardProps) => { + const summary = getStatusSummary(proforma); + + return ( + + + {summary.title} + {summary.description} + + ); +}; diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-totals-inspector-card.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-totals-inspector-card.tsx new file mode 100644 index 00000000..9fa9d0f2 --- /dev/null +++ b/modules/customer-invoices/src/web/proformas/detail/ui/components/proforma-detail-totals-inspector-card.tsx @@ -0,0 +1,49 @@ +import { FieldValueList } from "@repo/rdx-ui/components"; + +import type { Proforma } from "../../../shared"; + +import { formatProformaMoney } from "./proforma-detail-formatters"; +import { ProformaDetailInspectorCard } from "./proforma-detail-inspector-card"; + +type ProformaDetailTotalsInspectorCardProps = { + proforma: Proforma; +}; + +export const ProformaDetailTotalsInspectorCard = ({ + proforma, +}: ProformaDetailTotalsInspectorCardProps) => { + const items = [ + { + label: "Subtotal", + value: formatProformaMoney( + proforma.subtotalAmount, + proforma.currencyCode, + proforma.languageCode + ), + }, + { + label: "Descuentos", + value: `-${formatProformaMoney(proforma.totalDiscountAmount, proforma.currencyCode, proforma.languageCode)}`, + valueClassName: "text-red-600", + }, + { + label: "IVA", + value: formatProformaMoney(proforma.ivaAmount, proforma.currencyCode, proforma.languageCode), + }, + { + label: "Total", + value: formatProformaMoney( + proforma.totalAmount, + proforma.currencyCode, + proforma.languageCode + ), + valueClassName: "text-lg font-semibold text-foreground", + }, + ]; + + return ( + + + + ); +}; diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/index.ts b/modules/customer-invoices/src/web/proformas/detail/ui/index.ts index c4e34b27..7559bbe2 100644 --- a/modules/customer-invoices/src/web/proformas/detail/ui/index.ts +++ b/modules/customer-invoices/src/web/proformas/detail/ui/index.ts @@ -1 +1,3 @@ +export * from "./blocks"; +export * from "./components"; export * from "./pages"; diff --git a/modules/customer-invoices/src/web/proformas/detail/ui/pages/proforma-detail-page.tsx b/modules/customer-invoices/src/web/proformas/detail/ui/pages/proforma-detail-page.tsx index d08e4110..859f3366 100644 --- a/modules/customer-invoices/src/web/proformas/detail/ui/pages/proforma-detail-page.tsx +++ b/modules/customer-invoices/src/web/proformas/detail/ui/pages/proforma-detail-page.tsx @@ -1,331 +1,20 @@ import { ErrorAlert, NotFoundCard } from "@erp/core/components"; -import { AppContent, BackHistoryButton, FieldValueList, PDFViewer } from "@repo/rdx-ui/components"; -import { MoneyHelper } from "@repo/rdx-utils"; -import { - Alert, - AlertDescription, - AlertTitle, - Button, - Card, - CardContent, - CardHeader, - CardTitle, - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - Separator, - Skeleton, - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@repo/shadcn-ui/components"; -import { cn } from "@repo/shadcn-ui/lib/utils"; -import { - CheckCircle2Icon, - DownloadIcon, - MoreHorizontalIcon, - PencilIcon, - RefreshCwIcon, - SquarePenIcon, -} from "lucide-react"; -import { type ReactNode, useMemo } from "react"; +import { AppContent, BackHistoryButton } from "@repo/rdx-ui/components"; import { IssueProformaDialog } from "../../../issue-proforma"; import { ProformaStatusBadge } from "../../../list/ui/components"; -import { type Proforma, type ProformaItem, ProformaPageHeader } from "../../../shared"; +import { ProformaPageHeader } from "../../../shared"; import { useProformaDetailPageController } from "../../controllers"; - -const ZOOM_OPTIONS = [ - { label: "Ajustar", value: "fit" }, - { label: "75%", value: "75" }, - { label: "90%", value: "90" }, - { label: "100%", value: "100" }, - { label: "125%", value: "125" }, -]; - -const DOCUMENT_WIDTH = 794; -const DOCUMENT_HEIGHT = 1123; - -const formatDate = (value: string | null | undefined) => { - if (!value) { - return "No informado"; - } - - const [year, month, day] = value.split("-"); - if (!(year && month && day)) { - return value; - } - - return `${day}/${month}/${year}`; -}; - -const formatMoney = (amount: number, currencyCode: string) => { - return MoneyHelper.formatCurrency(amount, 2, currencyCode); -}; - -const formatQuantity = (value: number | null) => { - if (value === null) { - return "-"; - } - - return new Intl.NumberFormat("es-ES", { - minimumFractionDigits: 0, - maximumFractionDigits: 2, - }).format(value); -}; - -const getStatusTone = (status: Proforma["status"]) => { - switch (status) { - case "approved": - return "border-emerald-200 bg-emerald-50 text-emerald-900"; - case "sent": - return "border-amber-200 bg-amber-50 text-amber-900"; - case "issued": - return "border-sky-200 bg-sky-50 text-sky-900"; - case "rejected": - return "border-red-200 bg-red-50 text-red-900"; - default: - return "border-slate-200 bg-slate-50 text-slate-900"; - } -}; - -const getStatusSummary = (proforma: Proforma) => { - if (proforma.isArchived) { - return { - title: "Proforma archivada", - description: "Está fuera del flujo principal y puedes recuperarla cuando lo necesites.", - }; - } - - switch (proforma.status) { - case "approved": - return { - title: "Lista para convertirse en factura", - description: "La proforma ya está aprobada y puede emitirse como factura.", - }; - case "sent": - return { - title: "Pendiente de respuesta del cliente", - description: "Se ha enviado y está a la espera de aceptación o rechazo.", - }; - case "issued": - return { - title: "Convertida en factura", - description: "La proforma ya ha sido emitida y forma parte del flujo fiscal.", - }; - case "rejected": - return { - title: "Requiere revisión", - description: "El cliente la ha rechazado y conviene revisar condiciones o importes.", - }; - default: - return { - title: "Pendiente de revisión", - description: "Sigue en borrador y todavía puede ajustarse antes de enviarla.", - }; - } -}; - - -const InspectorCard = ({ - title, - onEdit, - children, -}: { - title: string; - onEdit?: () => void; - children: ReactNode; -}) => { - return ( - - - {title} - {onEdit ? ( - - ) : null} - - {children} - - ); -}; - -const StatusInspectorCard = ({ proforma }: { proforma: Proforma }) => { - const summary = getStatusSummary(proforma); - - return ( - - - {summary.title} - {summary.description} - - ); -}; - -const BillingInspectorCard = ({ - proforma, - paymentMethodLabel, - paymentTermLabel, - onEdit, -}: { - proforma: Proforma; - paymentMethodLabel: string | null; - paymentTermLabel: string | null; - onEdit: () => void; -}) => { - const items = [ - { - label: "Método de pago", - value: paymentMethodLabel ?? "No informado", - }, - { - label: "Plazo", - value: paymentTermLabel ?? "No informado", - }, - { - label: "Moneda", - value: proforma.currencyCode, - }, - ]; - - return ( - - - - ); -}; - -const DatesInspectorCard = ({ proforma, onEdit }: { proforma: Proforma; onEdit: () => void }) => { - const items = [ - { - label: "Fecha proforma", - value: formatDate(proforma.proformaDate), - }, - { - label: "Fecha operación", - value: formatDate(proforma.operationDate), - }, - { - label: "Serie", - value: proforma.series ?? "No informada", - }, - { - label: "Referencia interna", - value: proforma.reference ?? "Sin referencia", - }, - ]; - - return ( - - - - ); -}; - -const NotesInspectorCard = ({ proforma, onEdit }: { proforma: Proforma; onEdit: () => void }) => { - return ( - -
-
-

Descripción

-

{proforma.description ?? "Sin descripción."}

-
- -
-

Observaciones

-

{proforma.notes ?? "Sin notas adicionales."}

-
-
-
- ); -}; - -const TotalsInspectorCard = ({ proforma }: { proforma: Proforma }) => { - const items = [ - { - label: "Subtotal", - value: formatMoney(proforma.subtotalAmount, proforma.currencyCode), - }, - { - label: "Descuentos", - value: `-${formatMoney(proforma.totalDiscountAmount, proforma.currencyCode)}`, - valueClassName: "text-red-600", - }, - { - label: "IVA", - value: formatMoney(proforma.ivaAmount, proforma.currencyCode), - }, - { - label: "Total", - value: formatMoney(proforma.totalAmount, proforma.currencyCode), - valueClassName: "text-lg font-semibold text-foreground", - }, - ]; - - return ( - - - - ); -}; - -const ProformaDetailSkeleton = () => { - return ( -
- - - - - - - - -
- - - - - - - -
- - - - - -
-
-
- ); -}; - -const HeaderMeta = ({ proforma }: { proforma: Proforma }) => { - const items = useMemo( - () => [ - proforma.recipient.name ?? "Cliente sin nombre", - formatDate(proforma.proformaDate), - `Total ${formatMoney(proforma.totalAmount, proforma.currencyCode)}`, - ], - [proforma] - ); - - return ( -
- {items.map((item, index) => ( -
- {index > 0 ? | : null} - {item} -
- ))} -
- ); -}; +import { + ProformaDetailHeaderActions, + ProformaDetailPreviewCard, + ProformaDetailSidebar, +} from "../blocks"; +import { + ProformaDetailArchivedBadge, + ProformaDetailHeaderMeta, + ProformaDetailSkeleton, +} from "../components"; export const ProformaDetailPage = () => { const ctrl = useProformaDetailPageController(); @@ -368,46 +57,14 @@ export const ProformaDetailPage = () => {
- - - - - - - - - - - } - /> - - - Duplicar proforma - - - {ctrl.proforma.isArchived ? "Desarchivar" : "Archivar"} - - - -
+ } backLabel="Volver al listado" onBack={ctrl.navigateBack} @@ -416,80 +73,35 @@ export const ProformaDetailPage = () => {
- + {ctrl.proforma.isArchived ? : null} +
- - - - - + -
- - - - - - - - - - - - - Líneas incluidas - - - - - - Concepto - Total - - - - {ctrl.proforma.items.slice(0, 4).map((item: ProformaItem) => ( - - - {item.description ?? "Sin descripción"} - - - {formatMoney(item.totalAmount, ctrl.proforma.currencyCode)} - - - ))} - -
-
-
-
+