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:
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
8
todo.md
8
todo.md
@@ -230,3 +230,11 @@
|
||||
- [x] Transformer la cellule Ventilation en menu déroulant éditable
|
||||
- [x] Sauvegarder automatiquement les modifications (mutation updateFieldMutation)
|
||||
- [x] Afficher une notification de succès après sauvegarde
|
||||
|
||||
## Ajout dynamique depuis les menus déroulants
|
||||
- [x] Ajouter option "➜ Ajouter..." dans le menu Service
|
||||
- [x] Ajouter option "➜ Ajouter..." dans le menu Ventilation
|
||||
- [x] Créer une boîte de dialogue pour saisir le nouveau nom
|
||||
- [x] Sauvegarder la nouvelle entrée dans la base de données (createDepartmentMutation, createAllocationMutation)
|
||||
- [x] Sélectionner automatiquement la nouvelle valeur pour la facture
|
||||
- [x] Rafraîchir les listes après ajout (invalidate queries)
|
||||
|
||||
Reference in New Issue
Block a user