Implement archive and unarchive functionality for proformas, including UI dialogs and logic for handling draft proformas.
This commit is contained in:
parent
eb8cbd97f2
commit
1e0aadbfc6
@ -8,6 +8,7 @@
|
|||||||
- Archived proformas remain available through `GET /proformas/:id`.
|
- Archived proformas remain available through `GET /proformas/:id`.
|
||||||
- Deleted proformas remain excluded from every flow.
|
- Deleted proformas remain excluded from every flow.
|
||||||
- Numbering is never reused and `document_series.next_number` is untouched.
|
- Numbering is never reused and `document_series.next_number` is untouched.
|
||||||
|
- Archived `draft` proformas can be deleted directly without unarchiving.
|
||||||
|
|
||||||
## Endpoints
|
## Endpoints
|
||||||
|
|
||||||
|
|||||||
@ -155,3 +155,4 @@ Si devuelve filas:
|
|||||||
|
|
||||||
- una proforma creada nace con `archived_at = NULL`
|
- una proforma creada nace con `archived_at = NULL`
|
||||||
- el archivado posterior no altera `status`
|
- el archivado posterior no altera `status`
|
||||||
|
- una proforma `draft` archivada puede borrarse sin desarchivarse antes
|
||||||
|
|||||||
@ -10,8 +10,9 @@ Definir el borrado funcional de proformas sin romper trazabilidad ni numeracion.
|
|||||||
- `sent`, `approved`, `rejected` e `issued` devuelven conflicto
|
- `sent`, `approved`, `rejected` e `issued` devuelven conflicto
|
||||||
- `rejected` no se borra; queda reservado para una futura operacion de archivado
|
- `rejected` no se borra; queda reservado para una futura operacion de archivado
|
||||||
- el borrado es logico mediante `deleted_at`
|
- el borrado es logico mediante `deleted_at`
|
||||||
- las proformas archivadas no se exponen para borrado en UI V1
|
- el borrado de `draft` no depende de `archived_at`
|
||||||
- si una proforma draft esta archivada, debe desarchivarse antes de borrarla
|
- una proforma `draft` archivada puede borrarse directamente
|
||||||
|
- una proforma `rejected` archivada no puede borrarse
|
||||||
- la numeracion no se reutiliza
|
- la numeracion no se reutiliza
|
||||||
- no se decrementa `document_series.next_number`
|
- no se decrementa `document_series.next_number`
|
||||||
- no se borran fisicamente `proforma_items` ni `proforma_taxes`
|
- no se borran fisicamente `proforma_items` ni `proforma_taxes`
|
||||||
@ -38,5 +39,6 @@ Definir el borrado funcional de proformas sin romper trazabilidad ni numeracion.
|
|||||||
## UI
|
## UI
|
||||||
|
|
||||||
- la accion `Eliminar` solo debe mostrarse para proformas `draft`
|
- la accion `Eliminar` solo debe mostrarse para proformas `draft`
|
||||||
|
- en la vista de archivadas, `Eliminar` tambien debe mostrarse para `draft`
|
||||||
- la confirmacion debe advertir que la referencia no se reutilizara
|
- la confirmacion debe advertir que la referencia no se reutilizara
|
||||||
- en esta fase la accion solo esta disponible en el listado
|
- en esta fase la accion esta disponible en el listado activo y en archivadas para `draft`
|
||||||
|
|||||||
@ -170,6 +170,7 @@ Preparar el esquema fisico para separar:
|
|||||||
- `DELETE /proformas/:proforma_id` vuelve a estar operativo sobre persistencia V2
|
- `DELETE /proformas/:proforma_id` vuelve a estar operativo sobre persistencia V2
|
||||||
- el borrado es logico mediante `deleted_at`
|
- el borrado es logico mediante `deleted_at`
|
||||||
- el archivado es logico mediante `archived_at`
|
- el archivado es logico mediante `archived_at`
|
||||||
|
- el borrado de `draft` no depende de `archived_at`
|
||||||
- solo `draft` puede borrarse
|
- solo `draft` puede borrarse
|
||||||
- `rejected` sigue fuera del alcance y queda reservado para archivado futuro
|
- `rejected` sigue fuera del alcance y queda reservado para archivado futuro
|
||||||
- la validacion de borrado comprueba tambien `linked_invoice_id` y `issued_invoices.source_proforma_id`
|
- la validacion de borrado comprueba tambien `linked_invoice_id` y `issued_invoices.source_proforma_id`
|
||||||
|
|||||||
@ -7,3 +7,8 @@ SELECT id, proforma_reference, status, archived_at, deleted_at
|
|||||||
FROM proformas
|
FROM proformas
|
||||||
WHERE archived_at IS NOT NULL
|
WHERE archived_at IS NOT NULL
|
||||||
AND deleted_at IS NOT NULL;
|
AND deleted_at IS NOT NULL;
|
||||||
|
|
||||||
|
SELECT id, proforma_reference, status, archived_at, deleted_at
|
||||||
|
FROM proformas
|
||||||
|
WHERE deleted_at IS NOT NULL
|
||||||
|
AND status <> 'draft';
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export class ProformaDeleter implements IProformaDeleter {
|
|||||||
id: UniqueID;
|
id: UniqueID;
|
||||||
transaction: unknown;
|
transaction: unknown;
|
||||||
}): Promise<Result<void, Error>> {
|
}): Promise<Result<void, Error>> {
|
||||||
const proformaResult = await this.deps.repository.getByIdInCompany(
|
const proformaResult = await this.deps.repository.getByIdIncludingArchivedInCompany(
|
||||||
params.companyId,
|
params.companyId,
|
||||||
params.id,
|
params.id,
|
||||||
params.transaction
|
params.transaction
|
||||||
|
|||||||
@ -99,6 +99,9 @@
|
|||||||
"archive": {
|
"archive": {
|
||||||
"confirm_title": "Archive proforma",
|
"confirm_title": "Archive proforma",
|
||||||
"confirm_description": "The proforma will be hidden from the main list but kept for historical lookup.",
|
"confirm_description": "The proforma will be hidden from the main list but kept for historical lookup.",
|
||||||
|
"confirm": "Archive",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"submitting": "Archiving...",
|
||||||
"success_title": "Proforma archived",
|
"success_title": "Proforma archived",
|
||||||
"success_description": "The proforma was archived successfully.",
|
"success_description": "The proforma was archived successfully.",
|
||||||
"error_title": "The proforma could not be archived",
|
"error_title": "The proforma could not be archived",
|
||||||
@ -108,6 +111,9 @@
|
|||||||
"unarchive": {
|
"unarchive": {
|
||||||
"confirm_title": "Unarchive proforma",
|
"confirm_title": "Unarchive proforma",
|
||||||
"confirm_description": "The proforma will appear again in the main list.",
|
"confirm_description": "The proforma will appear again in the main list.",
|
||||||
|
"confirm": "Unarchive",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"submitting": "Unarchiving...",
|
||||||
"success_title": "Proforma unarchived",
|
"success_title": "Proforma unarchived",
|
||||||
"success_description": "The proforma was unarchived successfully.",
|
"success_description": "The proforma was unarchived successfully.",
|
||||||
"error_title": "The proforma could not be unarchived",
|
"error_title": "The proforma could not be unarchived",
|
||||||
@ -130,9 +136,12 @@
|
|||||||
"delete_proforma_dialog": {
|
"delete_proforma_dialog": {
|
||||||
"single_title": "Delete proforma",
|
"single_title": "Delete proforma",
|
||||||
"single_description": "This proforma will be deleted. Its reference will not be reused.",
|
"single_description": "This proforma will be deleted. Its reference will not be reused.",
|
||||||
|
"archived_single_title": "Delete archived proforma",
|
||||||
|
"archived_single_description": "The draft proforma will be deleted. Its reference will not be reused.",
|
||||||
"multiple_title": "Delete proformas",
|
"multiple_title": "Delete proformas",
|
||||||
"multiple_description": "{{count}} draft proformas will be deleted. Their references will not be reused.",
|
"multiple_description": "{{count}} draft proformas will be deleted. Their references will not be reused.",
|
||||||
"warning": "This action does not delete issued invoices and does not reuse numbering.",
|
"warning": "This action does not delete issued invoices and does not reuse numbering.",
|
||||||
|
"archived_warning": "This action does not physically erase its data, but it will no longer be available in operational lists.",
|
||||||
"confirm_delete": "Confirm deletion",
|
"confirm_delete": "Confirm deletion",
|
||||||
"deleting": "Deleting...",
|
"deleting": "Deleting...",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
|||||||
@ -99,6 +99,9 @@
|
|||||||
"archive": {
|
"archive": {
|
||||||
"confirm_title": "Archivar proforma",
|
"confirm_title": "Archivar proforma",
|
||||||
"confirm_description": "La proforma se ocultará del listado principal, pero seguirá conservada para consulta histórica.",
|
"confirm_description": "La proforma se ocultará del listado principal, pero seguirá conservada para consulta histórica.",
|
||||||
|
"confirm": "Archivar",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"submitting": "Archivando...",
|
||||||
"success_title": "Proforma archivada",
|
"success_title": "Proforma archivada",
|
||||||
"success_description": "La proforma se ha archivado correctamente.",
|
"success_description": "La proforma se ha archivado correctamente.",
|
||||||
"error_title": "No se pudo archivar la proforma",
|
"error_title": "No se pudo archivar la proforma",
|
||||||
@ -108,6 +111,9 @@
|
|||||||
"unarchive": {
|
"unarchive": {
|
||||||
"confirm_title": "Desarchivar proforma",
|
"confirm_title": "Desarchivar proforma",
|
||||||
"confirm_description": "La proforma volverá a aparecer en el listado principal.",
|
"confirm_description": "La proforma volverá a aparecer en el listado principal.",
|
||||||
|
"confirm": "Desarchivar",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"submitting": "Desarchivando...",
|
||||||
"success_title": "Proforma desarchivada",
|
"success_title": "Proforma desarchivada",
|
||||||
"success_description": "La proforma se ha desarchivado correctamente.",
|
"success_description": "La proforma se ha desarchivado correctamente.",
|
||||||
"error_title": "No se pudo desarchivar la proforma",
|
"error_title": "No se pudo desarchivar la proforma",
|
||||||
@ -131,9 +137,12 @@
|
|||||||
"delete_proforma_dialog": {
|
"delete_proforma_dialog": {
|
||||||
"single_title": "Eliminar proforma",
|
"single_title": "Eliminar proforma",
|
||||||
"single_description": "Se eliminará la proforma. Su referencia no se reutilizará.",
|
"single_description": "Se eliminará la proforma. Su referencia no se reutilizará.",
|
||||||
|
"archived_single_title": "Eliminar proforma archivada",
|
||||||
|
"archived_single_description": "Se eliminará la proforma en borrador. Su referencia no se reutilizará.",
|
||||||
"multiple_title": "Eliminar proformas",
|
"multiple_title": "Eliminar proformas",
|
||||||
"multiple_description": "Se eliminarán {{count}} proformas draft. Sus referencias no se reutilizarán.",
|
"multiple_description": "Se eliminarán {{count}} proformas draft. Sus referencias no se reutilizarán.",
|
||||||
"warning": "Esta acción no elimina facturas emitidas ni reutiliza numeración.",
|
"warning": "Esta acción no elimina facturas emitidas ni reutiliza numeración.",
|
||||||
|
"archived_warning": "Esta acción no borra físicamente sus datos, pero dejará de estar disponible en los listados operativos.",
|
||||||
"confirm_delete": "Confirmar eliminación",
|
"confirm_delete": "Confirmar eliminación",
|
||||||
"deleting": "Eliminando...",
|
"deleting": "Eliminando...",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
export interface DeleteProformaTarget {
|
export interface DeleteProformaTarget {
|
||||||
id: string;
|
id: string;
|
||||||
reference?: string;
|
reference?: string;
|
||||||
|
isArchived?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,9 +34,20 @@ export const DeleteProformaDialog = ({
|
|||||||
const total = targets.length;
|
const total = targets.length;
|
||||||
const isSingle = total === 1;
|
const isSingle = total === 1;
|
||||||
const firstTarget = targets[0];
|
const firstTarget = targets[0];
|
||||||
|
const isArchivedContext = targets.some((target) => target.isArchived);
|
||||||
|
|
||||||
|
const titleKey = isArchivedContext
|
||||||
|
? "proformas.delete_proforma_dialog.archived_single_title"
|
||||||
|
: "proformas.delete_proforma_dialog.single_title";
|
||||||
|
const descriptionKey = isArchivedContext
|
||||||
|
? "proformas.delete_proforma_dialog.archived_single_description"
|
||||||
|
: "proformas.delete_proforma_dialog.single_description";
|
||||||
|
const warningKey = isArchivedContext
|
||||||
|
? "proformas.delete_proforma_dialog.archived_warning"
|
||||||
|
: "proformas.delete_proforma_dialog.warning";
|
||||||
|
|
||||||
const title = isSingle
|
const title = isSingle
|
||||||
? t("proformas.delete_proforma_dialog.single_title", {
|
? t(titleKey, {
|
||||||
reference: getTargetLabel(firstTarget),
|
reference: getTargetLabel(firstTarget),
|
||||||
})
|
})
|
||||||
: t("proformas.delete_proforma_dialog.multiple_title", {
|
: t("proformas.delete_proforma_dialog.multiple_title", {
|
||||||
@ -44,7 +55,7 @@ export const DeleteProformaDialog = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const description = isSingle
|
const description = isSingle
|
||||||
? t("proformas.delete_proforma_dialog.single_description")
|
? t(descriptionKey)
|
||||||
: t("proformas.delete_proforma_dialog.multiple_description", {
|
: t("proformas.delete_proforma_dialog.multiple_description", {
|
||||||
count: total,
|
count: total,
|
||||||
});
|
});
|
||||||
@ -64,7 +75,7 @@ export const DeleteProformaDialog = ({
|
|||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
|
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{t("proformas.delete_proforma_dialog.warning")}
|
{t(warningKey)}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{total > 1 ? (
|
{total > 1 ? (
|
||||||
|
|||||||
@ -12,4 +12,5 @@ export const prepareDeleteProformaTargets = (
|
|||||||
const mapToDeleteProformaTarget = (proforma: Proforma | ProformaListRow): DeleteProformaTarget => ({
|
const mapToDeleteProformaTarget = (proforma: Proforma | ProformaListRow): DeleteProformaTarget => ({
|
||||||
id: proforma.id,
|
id: proforma.id,
|
||||||
reference: proforma.reference,
|
reference: proforma.reference,
|
||||||
|
isArchived: proforma.isArchived,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { showErrorToast, showSuccessToast } from "@repo/rdx-ui/helpers";
|
import { showErrorToast, showSuccessToast } from "@repo/rdx-ui/helpers";
|
||||||
import type { RightPanelMode } from "@repo/rdx-ui/hooks";
|
import type { RightPanelMode } from "@repo/rdx-ui/hooks";
|
||||||
import { useCallback } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
|
|
||||||
import { useTranslation } from "../../../i18n";
|
import { useTranslation } from "../../../i18n";
|
||||||
@ -46,73 +46,99 @@ export const useListProformasPageController = () => {
|
|||||||
initialOpen: proformaId !== "",
|
initialOpen: proformaId !== "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleArchive = useCallback(
|
const [archiveTarget, setArchiveTarget] = useState<ProformaListRow | null>(null);
|
||||||
async (proforma: ProformaListRow) => {
|
const [unarchiveTarget, setUnarchiveTarget] = useState<ProformaListRow | null>(null);
|
||||||
const confirmed = window.confirm(
|
|
||||||
`${t("proformas.archive.confirm_title")}\n\n${t("proformas.archive.confirm_description")}`
|
const openArchiveDialog = useCallback((proforma: ProformaListRow) => {
|
||||||
|
setArchiveTarget(proforma);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const closeArchiveDialog = useCallback(() => {
|
||||||
|
if (archiveMutation.isPending) return;
|
||||||
|
setArchiveTarget(null);
|
||||||
|
}, [archiveMutation.isPending]);
|
||||||
|
|
||||||
|
const confirmArchive = useCallback(async () => {
|
||||||
|
if (!archiveTarget) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await archiveMutation.mutateAsync({ proformaId: archiveTarget.id });
|
||||||
|
showSuccessToast(
|
||||||
|
t("proformas.archive.success_title"),
|
||||||
|
t("proformas.archive.success_description")
|
||||||
);
|
);
|
||||||
|
setArchiveTarget(null);
|
||||||
if (!confirmed) return;
|
} catch (error: unknown) {
|
||||||
|
const status = (error as { status?: number } | undefined)?.status;
|
||||||
try {
|
showErrorToast(
|
||||||
await archiveMutation.mutateAsync({ proformaId: proforma.id });
|
t("proformas.archive.error_title"),
|
||||||
showSuccessToast(
|
status === 409
|
||||||
t("proformas.archive.success_title"),
|
? t("proformas.archive.error_conflict_message")
|
||||||
t("proformas.archive.success_description")
|
: t("proformas.archive.error_unknown_message")
|
||||||
);
|
|
||||||
} catch (error: unknown) {
|
|
||||||
console.error(error);
|
|
||||||
const status = (error as { status?: number } | undefined)?.status;
|
|
||||||
showErrorToast(
|
|
||||||
t("proformas.archive.error_title"),
|
|
||||||
status === 409
|
|
||||||
? t("proformas.archive.error_conflict_message")
|
|
||||||
: t("proformas.archive.error_unknown_message")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[archiveMutation, t]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleUnarchive = useCallback(
|
|
||||||
async (proforma: ProformaListRow) => {
|
|
||||||
const confirmed = window.confirm(
|
|
||||||
`${t("proformas.unarchive.confirm_title")}\n\n${t("proformas.unarchive.confirm_description")}`
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}, [archiveMutation, archiveTarget, t]);
|
||||||
|
|
||||||
if (!confirmed) return;
|
const openUnarchiveDialog = useCallback((proforma: ProformaListRow) => {
|
||||||
|
setUnarchiveTarget(proforma);
|
||||||
|
}, []);
|
||||||
|
|
||||||
try {
|
const closeUnarchiveDialog = useCallback(() => {
|
||||||
await unarchiveMutation.mutateAsync({ proformaId: proforma.id });
|
if (unarchiveMutation.isPending) return;
|
||||||
showSuccessToast(
|
setUnarchiveTarget(null);
|
||||||
t("proformas.unarchive.success_title"),
|
}, [unarchiveMutation.isPending]);
|
||||||
t("proformas.unarchive.success_description")
|
|
||||||
);
|
const confirmUnarchive = useCallback(async () => {
|
||||||
} catch (error: unknown) {
|
if (!unarchiveTarget) {
|
||||||
const status = (error as { status?: number } | undefined)?.status;
|
return;
|
||||||
showErrorToast(
|
}
|
||||||
t("proformas.unarchive.error_title"),
|
|
||||||
status === 409
|
try {
|
||||||
? t("proformas.unarchive.error_conflict_message")
|
await unarchiveMutation.mutateAsync({ proformaId: unarchiveTarget.id });
|
||||||
: t("proformas.unarchive.error_unknown_message")
|
showSuccessToast(
|
||||||
);
|
t("proformas.unarchive.success_title"),
|
||||||
}
|
t("proformas.unarchive.success_description")
|
||||||
},
|
);
|
||||||
[t, unarchiveMutation]
|
setUnarchiveTarget(null);
|
||||||
);
|
} catch (error: unknown) {
|
||||||
|
const status = (error as { status?: number } | undefined)?.status;
|
||||||
|
showErrorToast(
|
||||||
|
t("proformas.unarchive.error_title"),
|
||||||
|
status === 409
|
||||||
|
? t("proformas.unarchive.error_conflict_message")
|
||||||
|
: t("proformas.unarchive.error_unknown_message")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [t, unarchiveMutation, unarchiveTarget]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
listCtrl,
|
listCtrl,
|
||||||
panelCtrl,
|
panelCtrl,
|
||||||
|
|
||||||
|
archiveDialogCtrl: {
|
||||||
|
open: archiveTarget !== null,
|
||||||
|
target: archiveTarget,
|
||||||
|
isSubmitting: archiveMutation.isPending,
|
||||||
|
openDialog: openArchiveDialog,
|
||||||
|
closeDialog: closeArchiveDialog,
|
||||||
|
confirmArchive,
|
||||||
|
},
|
||||||
deleteDialogCtrl,
|
deleteDialogCtrl,
|
||||||
issueDialogCtrl,
|
issueDialogCtrl,
|
||||||
changeStatusDialogCtrl,
|
changeStatusDialogCtrl,
|
||||||
|
unarchiveDialogCtrl: {
|
||||||
|
open: unarchiveTarget !== null,
|
||||||
|
target: unarchiveTarget,
|
||||||
|
isSubmitting: unarchiveMutation.isPending,
|
||||||
|
openDialog: openUnarchiveDialog,
|
||||||
|
closeDialog: closeUnarchiveDialog,
|
||||||
|
confirmUnarchive,
|
||||||
|
},
|
||||||
|
|
||||||
downloadPDFCtrl,
|
downloadPDFCtrl,
|
||||||
handleArchive,
|
|
||||||
handleDownloadPDF,
|
handleDownloadPDF,
|
||||||
handleUnarchive,
|
|
||||||
pdfDownloadingId: downloadPDFCtrl.loadingId,
|
pdfDownloadingId: downloadPDFCtrl.loadingId,
|
||||||
isPDFDownloading: downloadPDFCtrl.isLoading,
|
isPDFDownloading: downloadPDFCtrl.isLoading,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -457,7 +457,7 @@ export function useProformasGridColumns(
|
|||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
|
|
||||||
{/* Eliminar */}
|
{/* Eliminar */}
|
||||||
{isDraft && !proforma.isArchived && actionHandlers.onDeleteClick && (
|
{isDraft && actionHandlers.onDeleteClick && (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger
|
<TooltipTrigger
|
||||||
|
|||||||
@ -0,0 +1,60 @@
|
|||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
Button,
|
||||||
|
Spinner,
|
||||||
|
} from "@repo/shadcn-ui/components";
|
||||||
|
|
||||||
|
import { useTranslation } from "../../../../i18n";
|
||||||
|
import type { ProformaListRow } from "../../../shared";
|
||||||
|
|
||||||
|
interface ArchiveProformaDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
target: ProformaListRow | null;
|
||||||
|
isSubmitting: boolean;
|
||||||
|
onConfirm: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ArchiveProformaDialog = ({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
target,
|
||||||
|
isSubmitting,
|
||||||
|
onConfirm,
|
||||||
|
}: ArchiveProformaDialogProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertDialog onOpenChange={onOpenChange} open={open}>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>{t("proformas.archive.confirm_title")}</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
{t("proformas.archive.confirm_description")}
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<Button disabled={isSubmitting} onClick={() => onOpenChange(false)} variant="outline">
|
||||||
|
{t("proformas.archive.cancel")}
|
||||||
|
</Button>
|
||||||
|
<Button disabled={isSubmitting || !target} onClick={onConfirm}>
|
||||||
|
{isSubmitting ? (
|
||||||
|
<>
|
||||||
|
<Spinner aria-hidden className="mr-2 size-4" />
|
||||||
|
{t("proformas.archive.submitting")}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
t("proformas.archive.confirm")
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1 +1,3 @@
|
|||||||
|
export * from "./archive-proforma-dialog";
|
||||||
export * from "./proforma-status-badge";
|
export * from "./proforma-status-badge";
|
||||||
|
export * from "./unarchive-proforma-dialog";
|
||||||
|
|||||||
@ -0,0 +1,60 @@
|
|||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
Button,
|
||||||
|
Spinner,
|
||||||
|
} from "@repo/shadcn-ui/components";
|
||||||
|
|
||||||
|
import { useTranslation } from "../../../../i18n";
|
||||||
|
import type { ProformaListRow } from "../../../shared";
|
||||||
|
|
||||||
|
interface UnarchiveProformaDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
target: ProformaListRow | null;
|
||||||
|
isSubmitting: boolean;
|
||||||
|
onConfirm: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UnarchiveProformaDialog = ({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
target,
|
||||||
|
isSubmitting,
|
||||||
|
onConfirm,
|
||||||
|
}: UnarchiveProformaDialogProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AlertDialog onOpenChange={onOpenChange} open={open}>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>{t("proformas.unarchive.confirm_title")}</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
{t("proformas.unarchive.confirm_description")}
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<Button disabled={isSubmitting} onClick={() => onOpenChange(false)} variant="outline">
|
||||||
|
{t("proformas.unarchive.cancel")}
|
||||||
|
</Button>
|
||||||
|
<Button disabled={isSubmitting || !target} onClick={onConfirm}>
|
||||||
|
{isSubmitting ? (
|
||||||
|
<>
|
||||||
|
<Spinner aria-hidden className="mr-2 size-4" />
|
||||||
|
{t("proformas.unarchive.submitting")}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
t("proformas.unarchive.confirm")
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -34,6 +34,7 @@ import { prepareIssueProformaTarget } from "../../../issue-proforma/utils";
|
|||||||
import type { ProformaListRow } from "../../../shared";
|
import type { ProformaListRow } from "../../../shared";
|
||||||
import { useListProformasPageController } from "../../controllers";
|
import { useListProformasPageController } from "../../controllers";
|
||||||
import { ProformaSummaryPanel, ProformasGrid, useProformasGridColumns } from "../blocks";
|
import { ProformaSummaryPanel, ProformasGrid, useProformasGridColumns } from "../blocks";
|
||||||
|
import { ArchiveProformaDialog, UnarchiveProformaDialog } from "../components";
|
||||||
import { ProformaStatusBadge } from "../components";
|
import { ProformaStatusBadge } from "../components";
|
||||||
|
|
||||||
export const ListProformasPage = () => {
|
export const ListProformasPage = () => {
|
||||||
@ -50,9 +51,9 @@ export const ListProformasPage = () => {
|
|||||||
deleteDialogCtrl,
|
deleteDialogCtrl,
|
||||||
issueDialogCtrl,
|
issueDialogCtrl,
|
||||||
changeStatusDialogCtrl,
|
changeStatusDialogCtrl,
|
||||||
handleArchive,
|
archiveDialogCtrl,
|
||||||
handleDownloadPDF,
|
handleDownloadPDF,
|
||||||
handleUnarchive,
|
unarchiveDialogCtrl,
|
||||||
isPDFDownloading: isPdfDownloading,
|
isPDFDownloading: isPdfDownloading,
|
||||||
pdfDownloadingId,
|
pdfDownloadingId,
|
||||||
} = useListProformasPageController();
|
} = useListProformasPageController();
|
||||||
@ -70,12 +71,12 @@ export const ListProformasPage = () => {
|
|||||||
onEditClick: (proforma) => handleEditClick(proforma.id),
|
onEditClick: (proforma) => handleEditClick(proforma.id),
|
||||||
onIssueClick: (proformaRow) =>
|
onIssueClick: (proformaRow) =>
|
||||||
issueDialogCtrl.openDialog(prepareIssueProformaTarget(proformaRow)),
|
issueDialogCtrl.openDialog(prepareIssueProformaTarget(proformaRow)),
|
||||||
onArchiveClick: handleArchive,
|
onArchiveClick: archiveDialogCtrl.openDialog,
|
||||||
onDeleteClick: (proformaRow: ProformaListRow) =>
|
onDeleteClick: (proformaRow: ProformaListRow) =>
|
||||||
deleteDialogCtrl.openDialog(prepareDeleteProformaTargets(proformaRow)),
|
deleteDialogCtrl.openDialog(prepareDeleteProformaTargets(proformaRow)),
|
||||||
onChangeStatusClick: (proforma) =>
|
onChangeStatusClick: (proforma) =>
|
||||||
changeStatusDialogCtrl.openDialog(prepareChangeProformaStatusTargets(proforma)),
|
changeStatusDialogCtrl.openDialog(prepareChangeProformaStatusTargets(proforma)),
|
||||||
onUnarchiveClick: handleUnarchive,
|
onUnarchiveClick: unarchiveDialogCtrl.openDialog,
|
||||||
|
|
||||||
onDownloadPdf: handleDownloadPDF,
|
onDownloadPdf: handleDownloadPDF,
|
||||||
pdfDownloadingId,
|
pdfDownloadingId,
|
||||||
@ -360,6 +361,30 @@ export const ListProformasPage = () => {
|
|||||||
open={deleteDialogCtrl.open}
|
open={deleteDialogCtrl.open}
|
||||||
targets={deleteDialogCtrl.targets}
|
targets={deleteDialogCtrl.targets}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ArchiveProformaDialog
|
||||||
|
isSubmitting={archiveDialogCtrl.isSubmitting}
|
||||||
|
onConfirm={archiveDialogCtrl.confirmArchive}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open) {
|
||||||
|
archiveDialogCtrl.closeDialog();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
open={archiveDialogCtrl.open}
|
||||||
|
target={archiveDialogCtrl.target}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<UnarchiveProformaDialog
|
||||||
|
isSubmitting={unarchiveDialogCtrl.isSubmitting}
|
||||||
|
onConfirm={unarchiveDialogCtrl.confirmUnarchive}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open) {
|
||||||
|
unarchiveDialogCtrl.closeDialog();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
open={unarchiveDialogCtrl.open}
|
||||||
|
target={unarchiveDialogCtrl.target}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user