This commit is contained in:
Arthur Belleville 2025-04-13 17:50:11 +02:00
parent 14dde73fd2
commit 06e6e7d120
No known key found for this signature in database
6 changed files with 439 additions and 406 deletions

View file

@ -1,19 +1,18 @@
import React from "react";
import { Button } from "@ui/ui-library/button";
import { PencilIcon, TrashIcon } from "lucide-react";
import { DeleteDevisModal } from "@ui/components/devis/DeleteDevisModal";
import { ViewDevisModal } from "./devis/ViewDevisModal";
import { Database } from "@ui/types/db";
type Devis = Database["public"]["Tables"]["devis"]["Row"];
interface RowActionMenuProps {
devis: Devis;
onEdit: (devis: Devis) => void;
onDelete: (devis: Devis) => void;
// onEdit: (devis: Devis) => void;
onDelete: (devisId: string) => void;
}
export const RowActionMenu: React.FC<RowActionMenuProps> = ({
devis,
onEdit,
// onEdit,
onDelete,
}) => {
return (
@ -26,23 +25,8 @@ export const RowActionMenu: React.FC<RowActionMenuProps> = ({
onClick={(e) => e.stopPropagation()}
onDoubleClick={(e) => e.stopPropagation()}
>
<Button
variant="outline"
size="sm"
onPress={() => onEdit(devis)}
aria-label="Modifier le devis"
>
<PencilIcon className="w-4 h-4" />
</Button>
<Button
variant="outline"
color="destructive"
size="sm"
onPress={() => onDelete(devis)}
aria-label="Supprimer le devis"
>
<TrashIcon className="w-4 h-4" />
</Button>
<ViewDevisModal selectedDevis={devis} />
<DeleteDevisModal devisId={devis.id} onDelete={onDelete} />
</div>
);
};

View file

@ -0,0 +1,200 @@
import { Button } from "@ui/ui-library/button";
import {
Dialog,
DialogBody,
DialogCloseButton,
DialogFooter,
DialogHeader,
} from "@ui/ui-library/dialog";
import { Modal } from "@ui/ui-library/modal";
import { PlusIcon } from "lucide-react";
import { Form } from "@ui/ui-library/form";
import { DateField, DateInput } from "@ui/ui-library/date-field";
import { FieldError, Input, Label } from "@ui/ui-library/field";
import { TextField, TextArea } from "@ui/ui-library/field";
import { useState } from "react";
import { CalendarDate } from "@internationalized/date";
import { calculateTotal } from "@ui/utils/helpers";
import { calculateTax } from "@ui/utils/helpers";
const now = new Date();
const defaultFormData = {
client_email: "",
date: new CalendarDate(now.getFullYear(), now.getMonth(), now.getDate()),
due_date: new CalendarDate(
now.getFullYear(),
now.getMonth(),
now.getDate()
).add({ days: 30 }),
notes: "",
terms: "",
amount: 0,
tax_rate: 20,
};
export const CreateDevisModal = ({
handleCreate,
dueDateError,
setDueDateError,
}: {
handleCreate: (event: React.FormEvent<HTMLFormElement>) => void;
dueDateError: string;
setDueDateError: (error: string) => void;
}) => {
const [formData, setFormData] = useState(defaultFormData);
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button
color="accent"
className="px-4 bg-sky-900 hover:bg-accent-200 dark:bg-accent-900 dark:hover:bg-accent-800"
onPress={() => setIsOpen(true)}
aria-label="Créer un nouveau devis"
>
<PlusIcon />
Nouveau Devis
</Button>
<Modal size="lg" isDismissable isOpen={isOpen} onOpenChange={setIsOpen}>
<Dialog>
<DialogHeader>
<h2 className="text-xl font-semibold">Créer un nouveau devis</h2>
<DialogCloseButton />
</DialogHeader>
<DialogBody>
<Form onSubmit={handleCreate} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<DateField name="date" defaultValue={defaultFormData.date}>
<Label aria-required>Date</Label>
<DateInput />
</DateField>
<DateField
name="due_date"
defaultValue={defaultFormData.due_date}
isInvalid={!!dueDateError}
onChange={() => setDueDateError("")}
>
<Label aria-required>Date d&apos;échéance</Label>
<DateInput />
<FieldError>{dueDateError}</FieldError>
</DateField>
</div>
<TextField
name="client_email"
type="email"
isRequired
aria-label="client_email"
>
<Label aria-required>Email du client</Label>
<Input placeholder="Email" />
<FieldError>
{(validationState) => {
console.log(validationState);
return validationState.validationErrors
? "Veuillez entrer une adresse email valide"
: "";
}}
</FieldError>
</TextField>
<div className="grid grid-cols-2 gap-4">
<TextField
name="amount"
type="number"
isRequired
aria-label="amount"
defaultValue={formData.amount.toString()}
onChange={(value: string) => {
const amount = parseFloat(value) || 0;
setFormData((prev) => ({
...prev,
amount,
}));
}}
>
<Label aria-required>Montant HT</Label>
<Input placeholder="0.00" step="0.01" />
<FieldError>
{(validationState) => {
return validationState.validationErrors
? "Veuillez entrer un montant valide"
: "";
}}
</FieldError>
</TextField>
<TextField
name="tax_rate"
type="number"
isRequired
aria-label="tax_rate"
defaultValue={formData.tax_rate.toString()}
onChange={(value: string) => {
const tax_rate = parseFloat(value) || 0;
setFormData((prev) => ({
...prev,
tax_rate,
}));
}}
>
<Label aria-required>Taux de TVA (%)</Label>
<Input placeholder="20" />
<FieldError>
{(validationState) => {
return validationState.validationErrors
? "Veuillez entrer un taux valide"
: "";
}}
</FieldError>
</TextField>
</div>
<div className="bg-gray-50 dark:bg-gray-800 p-4 rounded-lg">
<div className="flex justify-between mb-2">
<span className="text-sm font-medium text-gray-500 dark:text-gray-400">
Montant HT
</span>
<span>{formData.amount.toFixed(2)} </span>
</div>
<div className="flex justify-between mb-2">
<span className="text-sm font-medium text-gray-500 dark:text-gray-400">
TVA ({formData.tax_rate}%)
</span>
<span>
{calculateTax(formData.amount, formData.tax_rate).toFixed(
2
)}{" "}
</span>
</div>
<div className="flex justify-between font-semibold border-t pt-2">
<span>Total TTC</span>
<span>
{calculateTotal(
formData.amount,
calculateTax(formData.amount, formData.tax_rate)
).toFixed(2)}{" "}
</span>
</div>
</div>
<TextField aria-label="notes">
<TextArea name="notes" placeholder="Notes" />
</TextField>
<TextField aria-label="terms">
<TextArea name="terms" placeholder="Conditions" />
</TextField>
<DialogFooter>
<DialogCloseButton variant="outline">Annuler</DialogCloseButton>
<Button
variant="solid"
color="accent"
type="submit"
slot="close"
>
Créer
</Button>
</DialogFooter>
</Form>
</DialogBody>
</Dialog>
</Modal>
</>
);
};

View file

@ -0,0 +1,65 @@
import { Button } from "@ui/ui-library/button";
import { DialogBody, DialogFooter } from "@ui/ui-library/dialog";
import { DialogCloseButton } from "@ui/ui-library/dialog";
import { DialogHeader } from "@ui/ui-library/dialog";
import { Dialog } from "@ui/ui-library/dialog";
import { TrashIcon } from "lucide-react";
import { Modal } from "@ui/ui-library/modal";
import { useState } from "react";
export const DeleteDevisModal = ({
devisId,
onDelete,
}: {
devisId: string | null;
onDelete: (devisId: string) => void;
}) => {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button
variant="outline"
color="destructive"
size="sm"
onPress={() => setIsOpen(true)}
className="bg-red-400 text-white hover:bg-red-500"
aria-label="Supprimer le devis"
>
<TrashIcon className="w-4 h-4" />
</Button>
<Modal isOpen={isOpen} onOpenChange={setIsOpen}>
<Dialog>
<DialogHeader slot="title">
<h2 className="text-xl font-semibold text-red-600">
Supprimer le devis
</h2>
<DialogCloseButton />
</DialogHeader>
<DialogBody>
<p className="text-gray-600 dark:text-gray-300">
Êtes-vous sûr de vouloir supprimer ce devis ? Cette action est
irréversible.
</p>
</DialogBody>
<DialogFooter>
<DialogCloseButton variant="outline">Annuler</DialogCloseButton>
<Button
variant="outline"
color="destructive"
onPress={() => {
if (devisId) {
onDelete(devisId);
}
}}
aria-label="Supprimer le devis"
>
Supprimer
</Button>
</DialogFooter>
</Dialog>
</Modal>
</>
);
};

View file

@ -0,0 +1,119 @@
import { Database } from "@ui/types/db";
import { Button } from "@ui/ui-library/button";
import { DialogBody, DialogFooter } from "@ui/ui-library/dialog";
import { DialogCloseButton } from "@ui/ui-library/dialog";
import { Dialog, DialogHeader } from "@ui/ui-library/dialog";
import { EyeIcon } from "lucide-react";
import { Modal } from "@ui/ui-library/modal";
import { useState } from "react";
type Devis = Database["public"]["Tables"]["devis"]["Row"];
export const ViewDevisModal = ({ selectedDevis }: { selectedDevis: Devis }) => {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button
variant="outline"
size="sm"
onPress={() => setIsOpen(true)}
aria-label="Modifier le devis"
>
<EyeIcon className="w-4 h-4" />
</Button>
<Modal size="lg" isOpen={isOpen} onOpenChange={setIsOpen} isDismissable>
<Dialog>
<DialogHeader slot="title">
<h2 className="text-xl font-semibold">
Devis {selectedDevis.number}
</h2>
<DialogCloseButton />
</DialogHeader>
<DialogBody>
<div className="space-y-6">
<div className="grid grid-cols-2 gap-4">
<div>
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">
Client
</h3>
<p className="mt-1">{selectedDevis.client_email}</p>
</div>
<div>
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">
Statut
</h3>
<p className="mt-1">{selectedDevis.status}</p>
</div>
<div>
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">
Date de création
</h3>
<p className="mt-1">
{selectedDevis.date
? new Date(selectedDevis.date).toLocaleDateString("fr-FR")
: ""}
</p>
</div>
<div>
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">
Date d&apos;échéance
</h3>
<p className="mt-1">
{selectedDevis.due_date
? new Date(selectedDevis.due_date).toLocaleDateString(
"fr-FR"
)
: ""}
</p>
</div>
</div>
{selectedDevis.notes && (
<div>
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">
Notes
</h3>
<p className="mt-1 whitespace-pre-wrap">
{selectedDevis.notes}
</p>
</div>
)}
{selectedDevis.terms && (
<div>
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">
Conditions
</h3>
<p className="mt-1 whitespace-pre-wrap">
{selectedDevis.terms}
</p>
</div>
)}
<div className="border-t pt-4">
<div className="flex justify-between">
<span className="text-sm font-medium text-gray-500 dark:text-gray-400">
Sous-total
</span>
<span>{selectedDevis.subtotal.toFixed(2)} </span>
</div>
<div className="flex justify-between mt-2">
<span className="text-sm font-medium text-gray-500 dark:text-gray-400">
TVA
</span>
<span>{selectedDevis.tax.toFixed(2)} </span>
</div>
<div className="flex justify-between mt-2 font-semibold">
<span>Total</span>
<span>{selectedDevis.total.toFixed(2)} </span>
</div>
</div>
</div>
</DialogBody>
<DialogFooter>
<DialogCloseButton>Fermer</DialogCloseButton>
</DialogFooter>
</Dialog>
</Modal>
</>
);
};

View file

@ -1,5 +1,4 @@
import { Button } from "@ui/ui-library/button";
import { NotebookPenIcon, PlusIcon } from "lucide-react";
import { NotebookPenIcon } from "lucide-react";
import {
AllCommunityModule,
ColDef,
@ -10,25 +9,7 @@ import { AgGridReact } from "ag-grid-react";
import { useDevisList, useCreateDevis, useDeleteDevis } from "@ui/hooks/devis";
import { useState } from "react";
import { Database } from "@ui/types/db";
import { Modal } from "@ui/ui-library/modal";
import {
Dialog,
DialogHeader,
DialogBody,
DialogFooter,
DialogCloseButton,
DialogTrigger,
} from "@ui/ui-library/dialog";
import {
TextField,
Input,
Label,
FieldError,
TextArea,
} from "@ui/ui-library/field";
import { CalendarDate, DateValue } from "@internationalized/date";
import { Form } from "@ui/ui-library/form";
import { DateField, DateInput } from "@ui/ui-library/date-field";
import { RowActionMenu } from "@ui/components/RowActionMenu";
import { CustomLoadingOverlay } from "@ui/components/CustomLoadingOverlay";
import {
@ -38,36 +19,21 @@ import {
EmptyStateHeading,
EmptyStateIcon,
} from "@ui/ui-library/empty-state";
import { CreateDevisModal } from "@ui/components/devis/CreateDevisModal";
import { calculateTotal } from "@ui/utils/helpers";
import { calculateTax } from "@ui/utils/helpers";
ModuleRegistry.registerModules([AllCommunityModule]);
type Devis = Database["public"]["Tables"]["devis"]["Row"];
const now = new Date();
const defaultFormData = {
client_email: "",
date: new CalendarDate(now.getFullYear(), now.getMonth(), now.getDate()),
due_date: new CalendarDate(
now.getFullYear(),
now.getMonth(),
now.getDate()
).add({ days: 30 }),
notes: "",
terms: "",
amount: 0,
tax_rate: 20,
};
export const DevisPage = () => {
const { data: devisData, isLoading } = useDevisList();
const createDevis = useCreateDevis();
const deleteDevis = useDeleteDevis();
const [dueDateError, setDueDateError] = useState("");
const [selectedDevis, setSelectedDevis] = useState<Devis | null>(null);
const [formData, setFormData] = useState(defaultFormData);
const [devisIdToDelete, setDevisIdToDelete] = useState<string | null>(null);
const [isConfirmDeleteModalOpen, setIsConfirmDeleteModalOpen] =
useState(false);
// const [selectedDevis, setSelectedDevis] = useState<Devis | null>(null);
// const [devisIdToDelete, setDevisIdToDelete] = useState<string | null>(null);
const validateDueDate = (date: DateValue, dueDate: DateValue) => {
if (dueDate.compare(date) < 0) {
@ -76,27 +42,19 @@ export const DevisPage = () => {
return "";
};
const calculateTax = (amount: number, taxRate: number) => {
return (amount * taxRate) / 100;
};
const calculateTotal = (amount: number, tax: number) => {
return amount + tax;
};
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
const handleCreate = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const form = event.currentTarget;
const formData = Object.fromEntries(new FormData(form));
const payload = Object.fromEntries(new FormData(form));
const email = formData.client_email as string;
const date = formData.date as string;
const due_date = formData.due_date as string;
const notes = formData.notes as string;
const terms = formData.terms as string;
const amount = parseFloat(formData.amount as string) || 0;
const tax_rate = parseFloat(formData.tax_rate as string) || 0;
const email = payload.client_email as string;
const date = payload.date as string;
const due_date = payload.due_date as string;
const notes = payload.notes as string;
const terms = payload.terms as string;
const amount = parseFloat(payload.amount as string) || 0;
const tax_rate = parseFloat(payload.tax_rate as string) || 0;
const tax = calculateTax(amount, tax_rate);
const total = calculateTotal(amount, tax);
@ -133,24 +91,14 @@ export const DevisPage = () => {
number: `DEV-${Date.now()}`,
});
form.reset();
setFormData(defaultFormData);
};
const handleEdit = (devis: Devis) => {
console.log("Edit devis:", devis);
};
// const handleEdit = (devis: Devis) => {
// console.log("Edit devis:", devis);
// };
const handleDelete = (devis: Devis) => {
setDevisIdToDelete(devis.id);
setIsConfirmDeleteModalOpen(true);
};
const confirmDeleteAction = () => {
if (devisIdToDelete) {
deleteDevis.mutate(devisIdToDelete);
setDevisIdToDelete(null);
setIsConfirmDeleteModalOpen(false);
}
const confirmDeleteAction = (devisId: string) => {
deleteDevis.mutate(devisId);
};
const columnDefs: ColDef<Devis>[] = [
@ -190,11 +138,7 @@ export const DevisPage = () => {
if (!params.data) return null;
return (
<div className="flex justify-center items-center h-full">
<RowActionMenu
devis={params.data}
onEdit={handleEdit}
onDelete={handleDelete}
/>
<RowActionMenu devis={params.data} onDelete={confirmDeleteAction} />
</div>
);
},
@ -215,165 +159,11 @@ export const DevisPage = () => {
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
Vos devis
</h1>
<DialogTrigger>
<Button
color="accent"
className="px-4 bg-sky-900 hover:bg-accent-200 dark:bg-accent-900 dark:hover:bg-accent-800"
aria-label="Créer un nouveau devis"
>
<PlusIcon />
Nouveau Devis
</Button>
<Modal size="lg" isDismissable>
<Dialog>
<DialogHeader>
<h2 className="text-xl font-semibold">
Créer un nouveau devis
</h2>
<DialogCloseButton />
</DialogHeader>
<DialogBody>
<Form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<DateField
name="date"
defaultValue={defaultFormData.date}
>
<Label aria-required>Date</Label>
<DateInput />
</DateField>
<DateField
name="due_date"
defaultValue={defaultFormData.due_date}
isInvalid={!!dueDateError}
onChange={() => setDueDateError("")}
>
<Label aria-required>Date d&apos;échéance</Label>
<DateInput />
<FieldError>{dueDateError}</FieldError>
</DateField>
</div>
<TextField
name="client_email"
type="email"
isRequired
aria-label="client_email"
>
<Label aria-required>Email du client</Label>
<Input placeholder="Email" />
<FieldError>
{(validationState) => {
console.log(validationState);
return validationState.validationErrors
? "Veuillez entrer une adresse email valide"
: "";
}}
</FieldError>
</TextField>
<div className="grid grid-cols-2 gap-4">
<TextField
name="amount"
type="number"
isRequired
aria-label="amount"
defaultValue={formData.amount.toString()}
onChange={(value: string) => {
const amount = parseFloat(value) || 0;
setFormData((prev) => ({
...prev,
amount,
}));
}}
>
<Label aria-required>Montant HT</Label>
<Input placeholder="0.00" step="0.01" />
<FieldError>
{(validationState) => {
return validationState.validationErrors
? "Veuillez entrer un montant valide"
: "";
}}
</FieldError>
</TextField>
<TextField
name="tax_rate"
type="number"
isRequired
aria-label="tax_rate"
defaultValue={formData.tax_rate.toString()}
onChange={(value: string) => {
const tax_rate = parseFloat(value) || 0;
setFormData((prev) => ({
...prev,
tax_rate,
}));
}}
>
<Label aria-required>Taux de TVA (%)</Label>
<Input placeholder="20" />
<FieldError>
{(validationState) => {
return validationState.validationErrors
? "Veuillez entrer un taux valide"
: "";
}}
</FieldError>
</TextField>
</div>
<div className="bg-gray-50 dark:bg-gray-800 p-4 rounded-lg">
<div className="flex justify-between mb-2">
<span className="text-sm font-medium text-gray-500 dark:text-gray-400">
Montant HT
</span>
<span>{formData.amount.toFixed(2)} </span>
</div>
<div className="flex justify-between mb-2">
<span className="text-sm font-medium text-gray-500 dark:text-gray-400">
TVA ({formData.tax_rate}%)
</span>
<span>
{calculateTax(
formData.amount,
formData.tax_rate
).toFixed(2)}{" "}
</span>
</div>
<div className="flex justify-between font-semibold border-t pt-2">
<span>Total TTC</span>
<span>
{calculateTotal(
formData.amount,
calculateTax(formData.amount, formData.tax_rate)
).toFixed(2)}{" "}
</span>
</div>
</div>
<TextField aria-label="notes">
<TextArea name="notes" placeholder="Notes" />
</TextField>
<TextField aria-label="terms">
<TextArea name="terms" placeholder="Conditions" />
</TextField>
<DialogFooter>
<DialogCloseButton variant="outline">
Annuler
</DialogCloseButton>
<Button
variant="solid"
color="accent"
type="submit"
slot="close"
>
Créer
</Button>
</DialogFooter>
</Form>
</DialogBody>
</Dialog>
</Modal>
</DialogTrigger>
<CreateDevisModal
handleCreate={handleCreate}
dueDateError={dueDateError}
setDueDateError={setDueDateError}
/>
</div>
</div>
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
@ -389,9 +179,11 @@ export const DevisPage = () => {
Créez un nouveau devis pour commencer.
</EmptyStateDescription>
<EmptyStateActions>
<DialogTrigger>
<Button>Créer un nouveau devis</Button>
</DialogTrigger>
<CreateDevisModal
handleCreate={handleCreate}
dueDateError={dueDateError}
setDueDateError={setDueDateError}
/>
</EmptyStateActions>
</EmptyState>
) : (
@ -401,11 +193,11 @@ export const DevisPage = () => {
loading={isLoading}
gridOptions={{
theme: themeQuartz,
onRowDoubleClicked: (event) => {
if (event.data) {
setSelectedDevis(event.data);
}
},
// onRowDoubleClicked: (event) => {
// if (event.data) {
// setSelectedDevis(event.data);
// }
// },
suppressHorizontalScroll: true,
domLayout: "autoHeight",
loadingOverlayComponent: CustomLoadingOverlay,
@ -427,145 +219,11 @@ export const DevisPage = () => {
</div>
</div>
</main>
<Modal
size="lg"
isOpen={!!selectedDevis}
onOpenChange={(isOpen) => !isOpen && setSelectedDevis(null)}
isDismissable
>
<Dialog>
<DialogHeader slot="title">
<h2 className="text-xl font-semibold">
Devis {selectedDevis?.number}
</h2>
<DialogCloseButton onPress={() => setSelectedDevis(null)} />
</DialogHeader>
<DialogBody>
<div className="space-y-6">
<div className="grid grid-cols-2 gap-4">
<div>
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">
Client
</h3>
<p className="mt-1">{selectedDevis?.client_email}</p>
</div>
<div>
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">
Statut
</h3>
<p className="mt-1">{selectedDevis?.status}</p>
</div>
<div>
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">
Date de création
</h3>
<p className="mt-1">
{selectedDevis?.date
? new Date(selectedDevis.date).toLocaleDateString("fr-FR")
: ""}
</p>
</div>
<div>
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">
Date d&apos;échéance
</h3>
<p className="mt-1">
{selectedDevis?.due_date
? new Date(selectedDevis.due_date).toLocaleDateString(
"fr-FR"
)
: ""}
</p>
</div>
</div>
{selectedDevis?.notes && (
<div>
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">
Notes
</h3>
<p className="mt-1 whitespace-pre-wrap">
{selectedDevis?.notes}
</p>
</div>
)}
{selectedDevis?.terms && (
<div>
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">
Conditions
</h3>
<p className="mt-1 whitespace-pre-wrap">
{selectedDevis?.terms}
</p>
</div>
)}
<div className="border-t pt-4">
<div className="flex justify-between">
<span className="text-sm font-medium text-gray-500 dark:text-gray-400">
Sous-total
</span>
<span>{selectedDevis?.subtotal.toFixed(2)} </span>
</div>
<div className="flex justify-between mt-2">
<span className="text-sm font-medium text-gray-500 dark:text-gray-400">
TVA
</span>
<span>{selectedDevis?.tax.toFixed(2)} </span>
</div>
<div className="flex justify-between mt-2 font-semibold">
<span>Total</span>
<span>{selectedDevis?.total.toFixed(2)} </span>
</div>
</div>
</div>
</DialogBody>
<DialogFooter>
<DialogCloseButton
variant="outline"
onPress={() => setSelectedDevis(null)}
>
Fermer
</DialogCloseButton>
</DialogFooter>
</Dialog>
</Modal>
<Modal
size="sm"
isOpen={isConfirmDeleteModalOpen}
onOpenChange={setIsConfirmDeleteModalOpen}
isDismissable
>
<Dialog>
<DialogHeader slot="title">
<h2 className="text-xl font-semibold text-red-600">
Supprimer le devis
</h2>
<DialogCloseButton
onPress={() => setIsConfirmDeleteModalOpen(false)}
/>
</DialogHeader>
<DialogBody>
<p className="text-gray-600 dark:text-gray-300">
Êtes-vous sûr de vouloir supprimer ce devis ? Cette action est
irréversible.
</p>
</DialogBody>
<DialogFooter>
<Button
variant="outline"
onPress={() => setIsConfirmDeleteModalOpen(false)}
>
Annuler
</Button>
<Button
variant="solid"
color="destructive"
onPress={confirmDeleteAction}
>
Supprimer
</Button>
</DialogFooter>
</Dialog>
</Modal>
{/* <ViewDevisModal
selectedDevis={selectedDevis}
setSelectedDevis={setSelectedDevis}
/> */}
</div>
);
};

7
ui/src/utils/helpers.ts Normal file
View file

@ -0,0 +1,7 @@
export const calculateTax = (amount: number, taxRate: number) => {
return (amount * taxRate) / 100;
};
export const calculateTotal = (amount: number, tax: number) => {
return amount + tax;
};