PROFORMAS CAMBIO DE ESTADO

This commit is contained in:
David Arranz 2026-04-05 22:30:01 +02:00
parent b2dee4ae8b
commit cd0940cb20
15 changed files with 285 additions and 197 deletions

View File

@ -120,6 +120,27 @@
"cancel": "Cancel", "cancel": "Cancel",
"continue": "Delete", "continue": "Delete",
"list_item": "Proforma {{reference}}" "list_item": "Proforma {{reference}}"
},
"change_proforma_status_dialog": {
"title": "Change proforma status",
"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.",
"cancel": "Cancel",
"confirm": "Change status",
"submitting": "Changing...",
"success_title": "Status updated successfully",
"success_single_description": "Proforma {{reference}} status has been updated successfully.",
"success_multiple_description": "The status of {{count}} proformas has been updated.",
"error_title": "Error changing status",
"error_single_description": "The status of the selected proforma could not be changed.",
"error_multiple_description": "The status of {{count}} proformas could not be changed.",
"error_unexpected_description": "An unexpected error has occurred.",
"legend": {
"current": "Current status",
"available": "Available statuses",
"unavailable": "Unavailable statuses"
}
} }
}, },
"pages": { "pages": {

View File

@ -121,6 +121,27 @@
"cancel": "Cancelar", "cancel": "Cancelar",
"continue": "Eliminar", "continue": "Eliminar",
"list_item": "Proforma {{reference}}" "list_item": "Proforma {{reference}}"
},
"change_proforma_status_dialog": {
"title": "Cambiar estado de la proforma",
"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.",
"cancel": "Cancelar",
"confirm": "Cambiar estado",
"submitting": "Cambiando...",
"success_title": "Estado actualizado correctamente",
"success_single_description": "La proforma {{reference}} ha cambiado de estado correctamente.",
"success_multiple_description": "Se ha actualizado el estado de {{count}} proformas.",
"error_title": "Error al cambiar el estado",
"error_single_description": "No se ha podido cambiar el estado de la proforma seleccionada.",
"error_multiple_description": "No se ha podido cambiar el estado de {{count}} proformas.",
"error_unexpected_description": "Ha ocurrido un error inesperado.",
"legend": {
"current": "Estado actual",
"available": "Estados disponibles",
"unavailable": "Estados no disponibles"
}
} }
}, },
"pages": { "pages": {

View File

@ -1,160 +1,122 @@
// proformas/change-status/controllers/use-change-proforma-status-dialog.controller.ts
import { showErrorToast, showSuccessToast } from "@repo/rdx-ui/helpers"; import { showErrorToast, showSuccessToast } from "@repo/rdx-ui/helpers";
import * as React from "react"; import { useState } from "react";
import { useTranslation } from "../../../i18n"; import { useTranslation } from "../../../i18n";
import type { ProformaListRow } from "../../shared"; import { type ProformaStatus, useProformaChangeStatusMutation } from "../../shared";
import type { PROFORMA_STATUS } from "../../shared/entities"; import type { ChangeProformaStatusTarget } from "../entities";
import { useChangeProformaStatusMutation } from "../../shared/hooks";
interface ChangeStatusDialogState { interface ChangeStatusDialogState {
open: boolean; open: boolean;
proformas: ProformaListRow[]; targets: ChangeProformaStatusTarget[];
isSubmitting: boolean;
} }
const INITIAL_STATE: ChangeStatusDialogState = { const INITIAL_STATE: ChangeStatusDialogState = {
open: false, open: false,
proformas: [], targets: [],
isSubmitting: false,
}; };
function canChangeStatus(isSubmitting: boolean, proformas: ProformaListRow[]): boolean { const canChangeStatus = (targets: ChangeProformaStatusTarget[]) => targets.length > 0;
return !isSubmitting && proformas.length > 0;
}
export function useChangeProformaStatusDialogController() { export const useChangeProformaStatusDialogController = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { changeProformaStatus } = useChangeProformaStatusMutation(); const changeStatusMutation = useProformaChangeStatusMutation();
const [state, setState] = React.useState<ChangeStatusDialogState>(INITIAL_STATE); const [state, setState] = useState<ChangeStatusDialogState>(INITIAL_STATE);
const { isSubmitting, proformas } = state;
const openDialog = (targets: ChangeProformaStatusTarget[]) => {
if (!canChangeStatus(targets)) {
return;
}
const openDialog = React.useCallback((proformas: ProformaListRow[]) => {
setState({ setState({
open: true, open: true,
proformas, targets,
isSubmitting: false,
}); });
}, []); };
const closeDialog = React.useCallback(() => { const closeDialog = () => {
setState(INITIAL_STATE); setState(INITIAL_STATE);
}, []); };
const changeStatusSelectedProformas = React.useCallback( const notifyChangeResult = (
async (proformas: ProformaListRow[], newStatus: PROFORMA_STATUS) => { targets: ChangeProformaStatusTarget[],
successCount: number,
errorCount: number
) => {
if (targets.length === 1 && successCount === 1) {
const target = targets[0];
showSuccessToast(
t("proformas.change_proforma_status_dialog.success_title"),
t("proformas.change_proforma_status_dialog.success_single_description", {
reference: target.reference || `#${target.id}`,
})
);
} else if (successCount > 0) {
showSuccessToast(
t("proformas.change_proforma_status_dialog.success_title"),
t("proformas.change_proforma_status_dialog.success_multiple_description", {
count: successCount,
})
);
}
if (errorCount > 0) {
showErrorToast(
t("proformas.change_proforma_status_dialog.error_title"),
targets.length === 1
? t("proformas.change_proforma_status_dialog.error_single_description")
: t("proformas.change_proforma_status_dialog.error_multiple_description", {
count: errorCount,
})
);
}
};
const confirmChangeStatus = async (newStatus: ProformaStatus) => {
const targets = state.targets;
if (!canChangeStatus(targets) || changeStatusMutation.isPending) {
return;
}
try {
const results = await Promise.allSettled( const results = await Promise.allSettled(
proformas.map((proforma) => targets.map((target) =>
changeProformaStatus({ changeStatusMutation.mutateAsync({
proformaId: proforma.id, id: target.id,
newStatus, data: {
new_status: newStatus,
},
}) })
) )
); );
const successCount = results.filter((result) => result.status === "fulfilled").length; const successCount = results.filter((result) => result.status === "fulfilled").length;
const errorCount = results.length - successCount; const errorCount = results.length - successCount;
return { notifyChangeResult(targets, successCount, errorCount);
successCount,
errorCount,
};
},
[changeProformaStatus]
);
const notifyChangeResult = React.useCallback( if (errorCount === 0) {
(proformas: ProformaListRow[], successCount: number, errorCount: number) => { closeDialog();
if (proformas.length === 1 && successCount === 1) {
const proforma = proformas[0];
showSuccessToast(
t("pages.proformas.change_status.successTitle"),
t("pages.proformas.change_status.successSingleMessage", {
reference: proforma.reference || `#${proforma.id}`,
})
);
} else if (successCount > 0) {
showSuccessToast(
t("pages.proformas.change_status.successTitle"),
t("pages.proformas.change_status.successMultipleMessage", {
count: successCount,
})
);
} }
} catch {
if (errorCount > 0) { showErrorToast(
showErrorToast( t("proformas.change_proforma_status_dialog.error_title"),
t("pages.proformas.change_status.errorTitle"), t("proformas.change_proforma_status_dialog.error_unexpected_description")
proformas.length === 1 );
? t("pages.proformas.change_status.errorSingleMessage") }
: t("pages.proformas.change_status.errorMultipleMessage", { };
count: errorCount,
})
);
}
},
[t]
);
const changeStatus = React.useCallback(
async (newStatus: PROFORMA_STATUS) => {
setState((current) => ({
...current,
isSubmitting: true,
}));
try {
const { successCount, errorCount } = await changeStatusSelectedProformas(
proformas,
newStatus
);
notifyChangeResult(proformas, successCount, errorCount);
if (errorCount === 0) {
closeDialog();
return;
}
setState((current) => ({
...current,
isSubmitting: false,
}));
} catch {
showErrorToast(
t("pages.proformas.change_status.errorTitle"),
t("pages.proformas.change_status.errorUnexpectedMessage")
);
setState((current) => ({
...current,
isSubmitting: false,
}));
}
},
[closeDialog, changeStatusSelectedProformas, notifyChangeResult, proformas, t]
);
const confirmChangeStatus = React.useCallback(
async (newStatus: PROFORMA_STATUS) => {
if (!canChangeStatus(isSubmitting, proformas)) {
return;
}
await changeStatus(newStatus);
},
[changeStatus, isSubmitting, proformas]
);
return { return {
open: state.open, open: state.open,
proformas: state.proformas, targets: state.targets,
isSubmitting: state.isSubmitting, isSubmitting: changeStatusMutation.isPending,
isBulkDelete: state.proformas.length > 1, isBulkChange: state.targets.length > 1,
openDialog, openDialog,
closeDialog, closeDialog,
confirmChangeStatus, confirmChangeStatus,
}; };
} };

View File

@ -0,0 +1,11 @@
import type { ProformaStatus } from "../../shared";
/**
* Representa la proforma a la que se le va a cambiar de estado.
*/
export interface ChangeProformaStatusTarget {
id: string;
reference: string;
status: ProformaStatus;
}

View File

@ -0,0 +1 @@
export * from './change-proforma-status-target.entity';

View File

@ -1,3 +1,4 @@
// proformas/change-status/helpers/proforma-status-ui.ts
import { import {
CheckCircle2Icon, CheckCircle2Icon,
FileCheckIcon, FileCheckIcon,

View File

@ -8,21 +8,22 @@ import {
DialogTitle, DialogTitle,
Spinner, Spinner,
} from "@repo/shadcn-ui/components"; } from "@repo/shadcn-ui/components";
import { useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "../../../i18n"; import { useTranslation } from "../../../i18n";
import { PROFORMA_STATUS, PROFORMA_STATUS_TRANSITIONS, type ProformaListRow } from "../../shared"; import { PROFORMA_STATUS, type ProformaStatus } from "../../shared";
import type { ChangeProformaStatusTarget } from "../entities";
import { getProformaStatusIcon } from "../helpers"; import { getProformaStatusIcon } from "../helpers";
import { getCommonAvailableProformaStatuses } from "../utils";
import { StatusNode, TimelineConnector } from "./components"; import { StatusLegend, StatusNode, TimelineConnector } from "./components";
interface ChangeStatusDialogProps { interface ChangeProformaStatusDialogProps {
open: boolean; open: boolean;
onOpenChange: (open: boolean) => void; onOpenChange: (open: boolean) => void;
proformas: ProformaListRow[]; targets: ChangeProformaStatusTarget[];
targetStatus?: string;
isSubmitting: boolean; isSubmitting: boolean;
onConfirm: (status: PROFORMA_STATUS) => void; onConfirm: (status: ProformaStatus) => void;
} }
const STATUS_FLOW = [ const STATUS_FLOW = [
@ -63,84 +64,86 @@ const STATUS_FLOW = [
}, },
] as const; ] as const;
export function ChangeStatusDialog({ export const ChangeProformaStatusDialog = ({
open, open,
onOpenChange, onOpenChange,
proformas, targets,
isSubmitting, isSubmitting,
onConfirm, onConfirm,
}: ChangeStatusDialogProps) { }: ChangeProformaStatusDialogProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [selectedStatus, setSelectedStatus] = useState<PROFORMA_STATUS | null>(null); const [selectedStatus, setSelectedStatus] = useState<ProformaStatus | null>(null);
// Obtener estados disponibles comunes para todas las proformas seleccionadas useEffect(() => {
const getAvailableStatuses = () => { if (!open) {
if (!proformas || proformas.length === 0) return []; setSelectedStatus(null);
// Intersección de estados disponibles para todas las proformas
const firstProforma = proformas[0];
let availableStatuses =
PROFORMA_STATUS_TRANSITIONS[firstProforma.status as PROFORMA_STATUS] || [];
for (let i = 1; i < proformas.length; i++) {
const currentStatuses =
PROFORMA_STATUS_TRANSITIONS[proformas[i].status as PROFORMA_STATUS] || [];
availableStatuses = availableStatuses.filter((status) => currentStatuses.includes(status));
} }
}, [open]);
return availableStatuses; const availableStatuses = useMemo(() => getCommonAvailableProformaStatuses(targets), [targets]);
};
const availableStatuses = getAvailableStatuses(); const currentStatus = targets.length === 1 ? targets[0].status : null;
const currentStatus = proformas.length === 1 ? proformas[0].status : null;
const getStatusType = ( const getStatusType = (
status: PROFORMA_STATUS status: ProformaStatus
): "past" | "current" | "available" | "unavailable" => { ): "past" | "current" | "available" | "unavailable" => {
if (currentStatus === status) return "current"; if (currentStatus === status) {
return "current";
}
// Si no hay un estado actual único, solo mostrar disponibles
if (!currentStatus) { if (!currentStatus) {
return availableStatuses.includes(status) ? "available" : "unavailable"; return availableStatuses.includes(status) ? "available" : "unavailable";
} }
// Determinar si es pasado basado en el flujo lógico const currentIndex = STATUS_FLOW.findIndex((item) => item.status === currentStatus);
const currentIndex = STATUS_FLOW.findIndex((s) => s.status === currentStatus); const statusIndex = STATUS_FLOW.findIndex((item) => item.status === status);
const statusIndex = STATUS_FLOW.findIndex((s) => s.status === status);
if (statusIndex < currentIndex) {
return "past";
}
if (availableStatuses.includes(status)) {
return "available";
}
if (statusIndex < currentIndex) return "past";
if (availableStatuses.includes(status)) return "available";
return "unavailable"; return "unavailable";
}; };
return ( return (
<Dialog <Dialog
onOpenChange={(nextOpen) => { onOpenChange={(nextOpen) => {
if (isSubmitting) return; if (isSubmitting) {
return;
}
onOpenChange(nextOpen); onOpenChange(nextOpen);
}} }}
open={open} open={open}
> >
<DialogContent className="sm:max-w-4xl"> <DialogContent className="sm:max-w-4xl">
<DialogHeader> <DialogHeader>
<DialogTitle>Cambiar estado de la proforma</DialogTitle> <DialogTitle>{t("proformas.change_proforma_status_dialog.title")}</DialogTitle>
<DialogDescription> <DialogDescription>
{proformas.length === 1 {targets.length === 1
? `Selecciona el nuevo estado para la proforma #${proformas[0].reference}` ? t("proformas.change_proforma_status_dialog.single_description", {
: `Selecciona el nuevo estado para ${proformas.length} proformas seleccionadas`} reference: targets[0].reference,
})
: t("proformas.change_proforma_status_dialog.multiple_description", {
count: targets.length,
})}
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
{availableStatuses.length === 0 ? ( {availableStatuses.length === 0 ? (
<div className="py-8 text-center text-sm text-muted-foreground"> <div className="py-8 text-center text-sm text-muted-foreground">
No hay transiciones de estado disponibles para las proformas seleccionadas. {t("proformas.change_proforma_status_dialog.empty_available_statuses")}
</div> </div>
) : ( ) : (
<div className="py-6"> <div className="py-6">
<div className="relative"> <div className="relative">
<div className="relative grid grid-cols-5 gap-2 mb-8"> <div className="relative mb-8 grid grid-cols-5 gap-2">
{STATUS_FLOW.map((statusConfig, index) => { {STATUS_FLOW.map((statusConfig) => {
const statusType = getStatusType(statusConfig.status); const statusType = getStatusType(statusConfig.status);
const isSelected = selectedStatus === statusConfig.status; const isSelected = selectedStatus === statusConfig.status;
const isClickable = statusType === "available"; const isClickable = statusType === "available";
@ -176,31 +179,39 @@ export function ChangeStatusDialog({
}))} }))}
/> />
</div> </div>
<StatusLegend hasCurrentStatus={currentStatus !== null} />
</div> </div>
)} )}
<DialogFooter className="sm:justify-between"> <DialogFooter className="sm:justify-between">
<Button disabled={isSubmitting} onClick={() => onOpenChange(false)} variant="outline"> <Button disabled={isSubmitting} onClick={() => onOpenChange(false)} variant="outline">
Cancelar {t("proformas.change_proforma_status_dialog.cancel")}
</Button> </Button>
<Button <Button
disabled={!selectedStatus || availableStatuses.length === 0 || isSubmitting} disabled={!selectedStatus || availableStatuses.length === 0 || isSubmitting}
onClick={() => { onClick={() => {
if (!selectedStatus) return; if (!selectedStatus) {
onConfirm(selectedStatus); // ← enviamos el estado al controlador return;
}
onConfirm(selectedStatus);
}} }}
> >
{isSubmitting ? ( {isSubmitting ? (
<> <>
<Spinner className="mr-2 size-4" /> <Spinner aria-hidden className="mr-2 size-4" />
Cambiando... <span aria-live="polite">
{t("proformas.change_proforma_status_dialog.submitting")}
</span>
</> </>
) : ( ) : (
"Cambiar estado" t("proformas.change_proforma_status_dialog.confirm")
)} )}
</Button> </Button>
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );
} };

View File

@ -1,5 +1,3 @@
"use client";
import { useTranslation } from "../../../../i18n"; import { useTranslation } from "../../../../i18n";
interface StatusLegendProps { interface StatusLegendProps {
@ -8,22 +6,31 @@ interface StatusLegendProps {
export const StatusLegend = ({ hasCurrentStatus }: StatusLegendProps) => { export const StatusLegend = ({ hasCurrentStatus }: StatusLegendProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div className="mt-8 p-4 bg-muted/50 rounded-lg"> <div className="mt-8 rounded-lg bg-muted/50 p-4">
<div className="flex items-start gap-6 text-sm"> <div className="flex items-start gap-6 text-sm">
{hasCurrentStatus && ( {hasCurrentStatus && (
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="size-3 rounded-full bg-amber-500 ring-2 ring-amber-200" /> <div className="size-3 rounded-full bg-amber-500 ring-2 ring-amber-200" />
<span className="text-muted-foreground">Estado actual</span> <span className="text-muted-foreground">
{t("proformas.change_proforma_status_dialog.legend.current")}
</span>
</div> </div>
)} )}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="size-3 rounded-full bg-linear-to-r from-blue-400 to-green-400" /> <div className="size-3 rounded-full bg-linear-to-r from-blue-400 to-green-400" />
<span className="text-muted-foreground">Estados disponibles (colores originales)</span> <span className="text-muted-foreground">
{t("proformas.change_proforma_status_dialog.legend.available")}
</span>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="size-3 rounded-full bg-gray-300 opacity-50" /> <div className="size-3 rounded-full bg-gray-300 opacity-50" />
<span className="text-muted-foreground">Estados no disponibles</span> <span className="text-muted-foreground">
{t("proformas.change_proforma_status_dialog.legend.unavailable")}
</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,13 +1,7 @@
"use client";
import { cn } from "@repo/shadcn-ui/lib/utils"; import { cn } from "@repo/shadcn-ui/lib/utils";
import { CheckCircle2, type LucideIcon } from "lucide-react"; import { CheckCircle2, type LucideIcon } from "lucide-react";
import { useTranslation } from "../../../../i18n";
import type { PROFORMA_STATUS } from "../../../shared";
interface StatusNodeProps { interface StatusNodeProps {
status: PROFORMA_STATUS;
label: string; label: string;
description: string; description: string;
icon: LucideIcon; icon: LucideIcon;
@ -21,7 +15,6 @@ interface StatusNodeProps {
} }
export const StatusNode = ({ export const StatusNode = ({
status,
label, label,
description, description,
icon: Icon, icon: Icon,
@ -33,7 +26,6 @@ export const StatusNode = ({
isSelected, isSelected,
onClick, onClick,
}: StatusNodeProps) => { }: StatusNodeProps) => {
const { t } = useTranslation();
const isClickable = statusType === "available"; const isClickable = statusType === "available";
return ( return (
@ -42,7 +34,7 @@ export const StatusNode = ({
className={cn( className={cn(
"relative z-10 flex size-24 flex-col items-center justify-center rounded-xl border-2 transition-all", "relative z-10 flex size-24 flex-col items-center justify-center rounded-xl border-2 transition-all",
statusType === "past" && ["border-gray-200 bg-gray-50", "opacity-50 saturate-50"], statusType === "past" && ["border-gray-200 bg-gray-50", "opacity-50 saturate-50"],
statusType === "current" && [activeBorder, activeBg, "border-4 scale-110 shadow-lg"], statusType === "current" && [activeBorder, activeBg, "scale-110 border-4 shadow-lg"],
statusType === "available" && [ statusType === "available" && [
activeBorder, activeBorder,
activeBg, activeBg,
@ -52,7 +44,7 @@ export const StatusNode = ({
], ],
statusType === "unavailable" && [ statusType === "unavailable" && [
"border-gray-200 bg-gray-50", "border-gray-200 bg-gray-50",
"opacity-30 saturate-0 cursor-not-allowed", "cursor-not-allowed opacity-30 saturate-0",
] ]
)} )}
disabled={!isClickable} disabled={!isClickable}
@ -61,7 +53,7 @@ export const StatusNode = ({
> >
<Icon <Icon
className={cn( className={cn(
"size-8 mb-1", "mb-1 size-8",
statusType === "past" && "text-gray-400", statusType === "past" && "text-gray-400",
statusType === "current" && activeColor, statusType === "current" && activeColor,
statusType === "available" && activeColor, statusType === "available" && activeColor,
@ -70,7 +62,7 @@ export const StatusNode = ({
/> />
{statusType === "current" && ( {statusType === "current" && (
<div className="absolute -top-3 left-1/2 -translate-x-1/2 px-2 py-0.5 rounded-full bg-primary text-primary-foreground text-[10px] font-bold tracking-wider shadow-md"> <div className="absolute -top-3 left-1/2 rounded-full bg-primary px-2 py-0.5 text-[10px] font-bold tracking-wider text-primary-foreground shadow-md -translate-x-1/2">
ACTUAL ACTUAL
</div> </div>
)} )}
@ -78,7 +70,7 @@ export const StatusNode = ({
{isSelected && statusType !== "current" && ( {isSelected && statusType !== "current" && (
<div <div
className={cn( className={cn(
"absolute -top-2 -right-2 size-6 rounded-full border-2 border-white flex items-center justify-center", "absolute -top-2 -right-2 flex size-6 items-center justify-center rounded-full border-2 border-white",
activeBg activeBg
)} )}
> >
@ -87,7 +79,7 @@ export const StatusNode = ({
)} )}
</button> </button>
<div className="mt-3 text-center space-y-1 px-1"> <div className="mt-3 space-y-1 px-1 text-center">
<p <p
className={cn( className={cn(
"text-sm font-medium", "text-sm font-medium",
@ -99,7 +91,8 @@ export const StatusNode = ({
> >
{label} {label}
</p> </p>
<p className="text-xs text-muted-foreground line-clamp-2">{description}</p>
<p className="line-clamp-2 text-xs text-muted-foreground">{description}</p>
</div> </div>
</div> </div>
); );

View File

@ -0,0 +1,20 @@
import { PROFORMA_STATUS_TRANSITIONS, type ProformaStatus } from "../../shared";
import type { ChangeProformaStatusTarget } from "../entities";
export const getCommonAvailableProformaStatuses = (
targets: ChangeProformaStatusTarget[]
): ProformaStatus[] => {
if (targets.length === 0) {
return [];
}
let availableStatuses = PROFORMA_STATUS_TRANSITIONS[targets[0].status] ?? [];
for (let index = 1; index < targets.length; index += 1) {
const currentStatuses = PROFORMA_STATUS_TRANSITIONS[targets[index].status] ?? [];
availableStatuses = availableStatuses.filter((status) => currentStatuses.includes(status));
}
return availableStatuses;
};

View File

@ -0,0 +1,2 @@
export * from './get-common-available-proforma-statuses';
export * from './prepare-change-proforma-status-targets';

View File

@ -0,0 +1,20 @@
import type { Proforma, ProformaListRow } from "../../shared";
import type { ChangeProformaStatusTarget } from "../entities";
type SupportedSource = Proforma | ProformaListRow;
type SupportedInput = SupportedSource | SupportedSource[];
const mapToChangeProformaStatusTarget = (
proforma: SupportedSource
): ChangeProformaStatusTarget => ({
id: proforma.id,
reference: proforma.reference,
status: proforma.status,
});
export const prepareChangeProformaStatusTargets = (
input: SupportedInput
): ChangeProformaStatusTarget[] => {
const items = Array.isArray(input) ? input : [input];
return items.map(mapToChangeProformaStatusTarget);
};

View File

@ -1,6 +1,7 @@
import type { RightPanelMode } from "@repo/rdx-ui/hooks"; import type { RightPanelMode } from "@repo/rdx-ui/hooks";
import { useSearchParams } from "react-router-dom"; import { useSearchParams } from "react-router-dom";
import { useChangeProformaStatusDialogController } from "../../change-status";
import { useDeleteProformaDialogController } from "../../delete"; import { useDeleteProformaDialogController } from "../../delete";
import { useIssueProformaDialogController } from "../../issue-proforma"; import { useIssueProformaDialogController } from "../../issue-proforma";
@ -11,6 +12,7 @@ export const useListProformasPageController = () => {
const listCtrl = useListProformasController(); const listCtrl = useListProformasController();
const deleteDialogCtrl = useDeleteProformaDialogController(); const deleteDialogCtrl = useDeleteProformaDialogController();
const issueDialogCtrl = useIssueProformaDialogController(); const issueDialogCtrl = useIssueProformaDialogController();
const changeStatusDialogCtrl = useChangeProformaStatusDialogController();
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
@ -29,5 +31,6 @@ export const useListProformasPageController = () => {
deleteDialogCtrl, deleteDialogCtrl,
issueDialogCtrl, issueDialogCtrl,
changeStatusDialogCtrl,
}; };
}; };

View File

@ -15,6 +15,8 @@ import { FilterIcon, PlusIcon } from "lucide-react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useTranslation } from "../../../../i18n"; import { useTranslation } from "../../../../i18n";
import { ChangeProformaStatusDialog } from "../../../change-status";
import { prepareChangeProformaStatusTargets } from "../../../change-status/utils";
import { DeleteProformaDialog } from "../../../delete"; import { DeleteProformaDialog } from "../../../delete";
import { prepareDeleteProformaTargets } from "../../../delete/utils"; import { prepareDeleteProformaTargets } from "../../../delete/utils";
import { IssueProformaDialog } from "../../../issue-proforma"; import { IssueProformaDialog } from "../../../issue-proforma";
@ -27,7 +29,7 @@ export const ListProformasPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const navigate = useNavigate(); const navigate = useNavigate();
const { listCtrl, panelCtrl, deleteDialogCtrl, issueDialogCtrl } = const { listCtrl, panelCtrl, deleteDialogCtrl, issueDialogCtrl, changeStatusDialogCtrl } =
useListProformasPageController(); useListProformasPageController();
const columns = useProformasGridColumns({ const columns = useProformasGridColumns({
@ -36,7 +38,8 @@ export const ListProformasPage = () => {
issueDialogCtrl.openDialog(prepareIssueProformaTarget(proformaRow)), issueDialogCtrl.openDialog(prepareIssueProformaTarget(proformaRow)),
onDeleteClick: (proformaRow: ProformaListRow) => onDeleteClick: (proformaRow: ProformaListRow) =>
deleteDialogCtrl.openDialog(prepareDeleteProformaTargets(proformaRow)), deleteDialogCtrl.openDialog(prepareDeleteProformaTargets(proformaRow)),
//onChangeStatusClick: handleChangeStatusProforma, onChangeStatusClick: (proforma) =>
changeStatusDialogCtrl.openDialog(prepareChangeProformaStatusTargets(proforma)),
}); });
const isPanelOpen = panelCtrl.panelState.isOpen; const isPanelOpen = panelCtrl.panelState.isOpen;
@ -162,6 +165,18 @@ export const ListProformasPage = () => {
target={issueDialogCtrl.target} target={issueDialogCtrl.target}
/> />
<ChangeProformaStatusDialog
isSubmitting={changeStatusDialogCtrl.isSubmitting}
onConfirm={changeStatusDialogCtrl.confirmChangeStatus}
onOpenChange={(open) => {
if (!open) {
changeStatusDialogCtrl.closeDialog();
}
}}
open={changeStatusDialogCtrl.open}
targets={changeStatusDialogCtrl.targets}
/>
{/* Eliminar */} {/* Eliminar */}
<DeleteProformaDialog <DeleteProformaDialog
isSecondConfirmStep={deleteDialogCtrl.isSecondConfirmStep} isSecondConfirmStep={deleteDialogCtrl.isSecondConfirmStep}

View File

@ -20,7 +20,7 @@ export const useProformaChangeStatusMutation = () => {
const dataSource = useDataSource(); const dataSource = useDataSource();
const schema = ChangeStatusProformaByIdRequestSchema; const schema = ChangeStatusProformaByIdRequestSchema;
useMutation< return useMutation<
ChangeProformaStatusByIdResult, ChangeProformaStatusByIdResult,
DefaultError, DefaultError,
ChangeProformaStatusByIdParams, ChangeProformaStatusByIdParams,