Checkpoint: Ajout d'une option "➜ Ajouter..." dans les menus déroulants Service et Ventilation de la liste des factures. Quand sélectionnée, une boîte de dialogue s'ouvre pour saisir le nouveau nom, qui est automatiquement ajouté à la liste et sélectionné pour la facture en cours.

This commit is contained in:
Manus
2026-02-11 09:18:38 -05:00
parent 69ba92b12a
commit 70d1cf23e5
2 changed files with 136 additions and 8 deletions

View File

@@ -15,6 +15,14 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import {
Table,
TableBody,
@@ -36,6 +44,10 @@ export default function Invoices() {
const [statusFilter, setStatusFilter] = useState<string>("all");
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const [invoiceToDelete, setInvoiceToDelete] = useState<number | null>(null);
const [addDialogOpen, setAddDialogOpen] = useState(false);
const [addDialogType, setAddDialogType] = useState<"service" | "ventilation" | null>(null);
const [newItemName, setNewItemName] = useState("");
const [pendingInvoiceId, setPendingInvoiceId] = useState<number | null>(null);
const { data: invoices, isLoading } = trpc.invoices.list.useQuery();
const { data: departments } = trpc.departments.getByUser.useQuery();
const { data: allocations } = trpc.accountingAllocations.getByUser.useQuery();
@@ -113,6 +125,58 @@ export default function Invoices() {
},
});
const createDepartmentMutation = trpc.departments.create.useMutation({
onSuccess: (newDept) => {
toast.success("Service ajouté avec succès");
utils.departments.getByUser.invalidate();
// Update the invoice with the new department
if (pendingInvoiceId) {
updateFieldMutation.mutate({
id: pendingInvoiceId,
data: { serviceConcerne: newDept.name },
});
}
setAddDialogOpen(false);
setNewItemName("");
setPendingInvoiceId(null);
},
onError: (error) => {
toast.error(error.message || "Erreur lors de l'ajout");
},
});
const createAllocationMutation = trpc.accountingAllocations.create.useMutation({
onSuccess: (newAlloc) => {
toast.success("Ventilation ajoutée avec succès");
utils.accountingAllocations.getByUser.invalidate();
// Update the invoice with the new allocation
if (pendingInvoiceId) {
updateFieldMutation.mutate({
id: pendingInvoiceId,
data: { ventilationComptable: newAlloc.name },
});
}
setAddDialogOpen(false);
setNewItemName("");
setPendingInvoiceId(null);
},
onError: (error) => {
toast.error(error.message || "Erreur lors de l'ajout");
},
});
const handleAddNewItem = () => {
if (!newItemName.trim()) {
toast.error("Le nom ne peut pas être vide");
return;
}
if (addDialogType === "service") {
createDepartmentMutation.mutate({ name: newItemName.trim() });
} else if (addDialogType === "ventilation") {
createAllocationMutation.mutate({ name: newItemName.trim() });
}
};
const filteredInvoices = invoices?.filter((inv) => {
// Filter by search query
if (searchQuery) {
@@ -381,10 +445,17 @@ export default function Invoices() {
<select
value={invoice.serviceConcerne || ""}
onChange={(e) => {
updateFieldMutation.mutate({
id: invoice.id,
data: { serviceConcerne: e.target.value || undefined },
});
if (e.target.value === "__ADD_NEW__") {
setPendingInvoiceId(invoice.id);
setAddDialogType("service");
setAddDialogOpen(true);
e.target.value = invoice.serviceConcerne || "";
} else {
updateFieldMutation.mutate({
id: invoice.id,
data: { serviceConcerne: e.target.value || undefined },
});
}
}}
className="w-full px-2 py-1 border rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
>
@@ -392,6 +463,7 @@ export default function Invoices() {
{departments?.map((dept) => (
<option key={dept.id} value={dept.name}>{dept.name}</option>
))}
<option value="__ADD_NEW__" className="font-semibold text-blue-600"> Ajouter...</option>
</select>
</TableCell>
<TableCell className="text-sm">
@@ -414,10 +486,17 @@ export default function Invoices() {
<select
value={invoice.ventilationComptable || ""}
onChange={(e) => {
updateFieldMutation.mutate({
id: invoice.id,
data: { ventilationComptable: e.target.value || undefined },
});
if (e.target.value === "__ADD_NEW__") {
setPendingInvoiceId(invoice.id);
setAddDialogType("ventilation");
setAddDialogOpen(true);
e.target.value = invoice.ventilationComptable || "";
} else {
updateFieldMutation.mutate({
id: invoice.id,
data: { ventilationComptable: e.target.value || undefined },
});
}
}}
className="w-full px-2 py-1 border rounded text-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
>
@@ -425,6 +504,7 @@ export default function Invoices() {
{allocations?.map((alloc) => (
<option key={alloc.id} value={alloc.name}>{alloc.name}</option>
))}
<option value="__ADD_NEW__" className="font-semibold text-blue-600"> Ajouter...</option>
</select>
</TableCell>
<TableCell>{getQualityBadge(invoice.qualityScore)}</TableCell>
@@ -505,6 +585,46 @@ export default function Invoices() {
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
{/* Add New Item Dialog */}
<Dialog open={addDialogOpen} onOpenChange={setAddDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>
Ajouter {addDialogType === "service" ? "un nouveau service" : "une nouvelle ventilation"}
</DialogTitle>
<DialogDescription>
Entrez le nom {addDialogType === "service" ? "du service" : "de la ventilation comptable"} à ajouter.
</DialogDescription>
</DialogHeader>
<div className="py-4">
<Input
placeholder="Nom..."
value={newItemName}
onChange={(e) => setNewItemName(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
handleAddNewItem();
}
}}
autoFocus
/>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => {
setAddDialogOpen(false);
setNewItemName("");
setPendingInvoiceId(null);
}}
>
Annuler
</Button>
<Button onClick={handleAddNewItem}>Ajouter</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</DashboardLayout>
);
}