Compare commits

..

2 Commits

Author SHA1 Message Date
ec1dd1099f Mejorar el diálogo de cambio de estado de proformas 2026-08-06 14:03:28 +02:00
d0779e2875 Revisar listado de clients 2026-08-06 13:50:06 +02:00
8 changed files with 69 additions and 21 deletions

View File

@ -160,6 +160,13 @@
"single_description": "Select the new status for proforma {{reference}}.",
"multiple_description": "Select the new status for {{count}} selected proformas.",
"empty_available_statuses": "No status transitions are available for the selected proformas.",
"status_explanations": {
"draft": "An unfinished proforma that is still pending changes.",
"sent": "The proforma has been sent to the customer for approval.",
"approved": "The proforma has been approved by the customer and can be turned into an invoice.",
"rejected": "The proforma has been rejected by the customer. It can stay like this or return to \"draft\".",
"issued": "The proforma has already been turned into an invoice."
},
"cancel": "Cancel",
"confirm": "Change status",
"submitting": "Changing...",

View File

@ -161,6 +161,13 @@
"single_description": "Selecciona el nuevo estado para la proforma {{reference}}.",
"multiple_description": "Selecciona el nuevo estado para {{count}} proformas seleccionadas.",
"empty_available_statuses": "No hay transiciones de estado disponibles para las proformas seleccionadas.",
"status_explanations": {
"draft": "Una proforma sin terminar y pendiente de modificaciones.",
"sent": "Se ha enviado la proforma al cliente para su aprobación.",
"approved": "La proforma ha sido aprobada por el cliente y puede pasar a factura.",
"rejected": "La proforma ha sido rechazada por el cliente. Puede quedarse así o volver a \"borrador\".",
"issued": "La proforma ya ha sido pasada a factura."
},
"cancel": "Cancelar",
"confirm": "Cambiar estado",
"submitting": "Cambiando...",

View File

@ -4,6 +4,7 @@ import {
FileCheckIcon,
FileQuestionIcon,
FileTextIcon,
ReceiptTextIcon,
SendIcon,
XCircleIcon,
} from "lucide-react";
@ -62,3 +63,24 @@ export const getProformaStatusIcon = (status: ProformaStatus) => {
return FileQuestionIcon;
}
};
export const getProformaStatusDisplayIcon = (status: ProformaStatus) => {
return status === "issued" ? ReceiptTextIcon : getProformaStatusIcon(status);
};
export const getProformaStatusBadgeClassName = (status: ProformaStatus): string => {
switch (status) {
case "draft":
return "border-gray-200 bg-gray-50 text-gray-600 ring-1 ring-inset ring-gray-200/90";
case "sent":
return "border-amber-300 bg-amber-50 text-amber-800 ring-1 ring-inset ring-amber-300/90";
case "approved":
return "border-emerald-300 bg-emerald-100/70 text-emerald-700 ring-1 ring-inset ring-emerald-300/90";
case "rejected":
return "border-red-300 bg-red-50 text-red-600 ring-1 ring-inset ring-red-300/90";
case "issued":
return "border-sky-300 bg-sky-100/70 text-sky-700 ring-1 ring-inset ring-sky-300/90";
default:
return "border-gray-200 bg-gray-50 text-gray-600 ring-1 ring-inset ring-gray-200/90";
}
};

View File

@ -15,7 +15,11 @@ import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "../../../i18n";
import { PROFORMA_STATUS, type ProformaStatus } from "../../shared";
import type { ChangeProformaStatusTarget } from "../entities";
import { getProformaStatusColor, getProformaStatusIcon } from "../helpers";
import {
getProformaStatusColor,
getProformaStatusDisplayIcon,
getProformaStatusIcon,
} from "../helpers";
import { getCommonAvailableProformaStatuses } from "../utils";
interface ChangeProformaStatusDialogProps {
@ -59,7 +63,7 @@ interface StatusOptionCardProps {
const StatusOptionCard = ({ status, selected, onClick }: StatusOptionCardProps) => {
const { t } = useTranslation();
const Icon = getProformaStatusIcon(status);
const Icon = getProformaStatusDisplayIcon(status);
return (
<button
@ -90,7 +94,7 @@ const StatusOptionCard = ({ status, selected, onClick }: StatusOptionCardProps)
</div>
<p className="text-sm text-muted-foreground">
{t(`catalog.proformas.status.${status}.description`)}
{t(`proformas.change_proforma_status_dialog.status_explanations.${status}`)}
</p>
</div>
</button>

View File

@ -1,9 +1,11 @@
import { Tooltip, TooltipContent, TooltipTrigger } from "@repo/shadcn-ui/components";
import { cn } from "@repo/shadcn-ui/lib/utils";
import { ReceiptTextIcon } from "lucide-react";
import { useTranslation } from "../../../../i18n";
import { getProformaStatusIcon } from "../../../change-status/helpers";
import {
getProformaStatusBadgeClassName,
getProformaStatusDisplayIcon,
} from "../../../change-status/helpers";
import type { ProformaStatus } from "../../../shared";
export type ProformaStatusBadgeProps = {
@ -14,20 +16,7 @@ export type ProformaStatusBadgeProps = {
export const ProformaStatusBadge = ({ status, className }: ProformaStatusBadgeProps) => {
const { t } = useTranslation();
const normalizedStatus = status.toLowerCase() as ProformaStatus;
const Icon = normalizedStatus === "issued" ? ReceiptTextIcon : getProformaStatusIcon(normalizedStatus);
const stylesByStatus: Record<ProformaStatus, string> = {
draft:
"border-gray-200 bg-gray-50 text-gray-600 ring-1 ring-inset ring-gray-200/90",
sent:
"border-amber-300 bg-amber-50 text-amber-800 ring-1 ring-inset ring-amber-300/90",
approved:
"border-emerald-300 bg-emerald-100/70 text-emerald-700 ring-1 ring-inset ring-emerald-300/90",
rejected:
"border-red-300 bg-red-50 text-red-600 ring-1 ring-inset ring-red-300/90",
issued:
"border-sky-300 bg-sky-100/70 text-sky-700 ring-1 ring-inset ring-sky-300/90",
};
const Icon = getProformaStatusDisplayIcon(normalizedStatus);
return (
<Tooltip>
@ -37,7 +26,7 @@ export const ProformaStatusBadge = ({ status, className }: ProformaStatusBadgePr
className={cn(
"inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium leading-none whitespace-nowrap",
"[&>svg]:pointer-events-none [&>svg]:shrink-0 [&>svg]:size-3.5",
stylesByStatus[normalizedStatus],
getProformaStatusBadgeClassName(normalizedStatus),
className
)}
>

View File

@ -71,6 +71,9 @@ export function useCustomersGridColumns(
enableHiding: false,
size: 140,
minSize: 120,
meta: {
title: t("list.columns.customer"),
},
cell: ({ row }) => {
const customer = row.original;
@ -131,6 +134,9 @@ export function useCustomersGridColumns(
enableSorting: false,
size: 140,
minSize: 120,
meta: {
title: t("list.columns.contact"),
},
cell: ({ row }) => <ContactCell contact={row.original.contact} />,
},
{
@ -147,12 +153,16 @@ export function useCustomersGridColumns(
enableSorting: false,
size: 140,
minSize: 120,
meta: {
title: t("list.columns.address"),
},
cell: ({ row }) => <AddressCell address={row.original.address} />,
},
{
id: "actions",
meta: {
isActionsColumn: true,
title: t("common.actions"),
headerClassName: "text-right",
},
header: "Acciones",

View File

@ -1,3 +1,4 @@
import { EmptyCellValue } from "@erp/core/components";
import { ExternalLinkIcon, MapPinIcon } from "lucide-react";
import { useTranslation } from "../../../i18n";
@ -37,6 +38,11 @@ export const AddressCell = ({ address }: { address: CustomerAddress }) => {
const line2 = join([address.postalCode, address.city], " ");
const line3 = join([address.province, address.country], ", ");
const line23 = join([line2, line3], " - ");
const hasAddress = Boolean(line1 || line23);
if (!hasAddress) {
return <EmptyCellValue />;
}
return (
<address className="not-italic flex items-start gap-2 text-muted-foreground hover:text-primary transition-colors">

View File

@ -1,3 +1,4 @@
import { EmptyCellValue } from "@erp/core/components";
import { GlobeIcon, MailIcon, PhoneIcon, PrinterIcon, SmartphoneIcon } from "lucide-react";
interface ContactCellData {
@ -66,7 +67,9 @@ export const ContactCell = ({ contact }: { contact: ContactCellData }) => {
: []),
];
if (items.length === 0) return null;
if (items.length === 0) {
return <EmptyCellValue />;
}
return (
<div className="flex flex-col gap-1.5 text-sm text-muted-foreground transition-colors">