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,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -36,6 +44,10 @@ export default function Invoices() {
|
|||||||
const [statusFilter, setStatusFilter] = useState<string>("all");
|
const [statusFilter, setStatusFilter] = useState<string>("all");
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
const [invoiceToDelete, setInvoiceToDelete] = useState<number | null>(null);
|
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: invoices, isLoading } = trpc.invoices.list.useQuery();
|
||||||
const { data: departments } = trpc.departments.getByUser.useQuery();
|
const { data: departments } = trpc.departments.getByUser.useQuery();
|
||||||
const { data: allocations } = trpc.accountingAllocations.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) => {
|
const filteredInvoices = invoices?.filter((inv) => {
|
||||||
// Filter by search query
|
// Filter by search query
|
||||||
if (searchQuery) {
|
if (searchQuery) {
|
||||||
@@ -381,10 +445,17 @@ export default function Invoices() {
|
|||||||
<select
|
<select
|
||||||
value={invoice.serviceConcerne || ""}
|
value={invoice.serviceConcerne || ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
updateFieldMutation.mutate({
|
if (e.target.value === "__ADD_NEW__") {
|
||||||
id: invoice.id,
|
setPendingInvoiceId(invoice.id);
|
||||||
data: { serviceConcerne: e.target.value || undefined },
|
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"
|
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) => (
|
{departments?.map((dept) => (
|
||||||
<option key={dept.id} value={dept.name}>{dept.name}</option>
|
<option key={dept.id} value={dept.name}>{dept.name}</option>
|
||||||
))}
|
))}
|
||||||
|
<option value="__ADD_NEW__" className="font-semibold text-blue-600">➜ Ajouter...</option>
|
||||||
</select>
|
</select>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-sm">
|
<TableCell className="text-sm">
|
||||||
@@ -414,10 +486,17 @@ export default function Invoices() {
|
|||||||
<select
|
<select
|
||||||
value={invoice.ventilationComptable || ""}
|
value={invoice.ventilationComptable || ""}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
updateFieldMutation.mutate({
|
if (e.target.value === "__ADD_NEW__") {
|
||||||
id: invoice.id,
|
setPendingInvoiceId(invoice.id);
|
||||||
data: { ventilationComptable: e.target.value || undefined },
|
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"
|
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) => (
|
{allocations?.map((alloc) => (
|
||||||
<option key={alloc.id} value={alloc.name}>{alloc.name}</option>
|
<option key={alloc.id} value={alloc.name}>{alloc.name}</option>
|
||||||
))}
|
))}
|
||||||
|
<option value="__ADD_NEW__" className="font-semibold text-blue-600">➜ Ajouter...</option>
|
||||||
</select>
|
</select>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{getQualityBadge(invoice.qualityScore)}</TableCell>
|
<TableCell>{getQualityBadge(invoice.qualityScore)}</TableCell>
|
||||||
@@ -505,6 +585,46 @@ export default function Invoices() {
|
|||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</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>
|
</DashboardLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
8
todo.md
8
todo.md
@@ -230,3 +230,11 @@
|
|||||||
- [x] Transformer la cellule Ventilation en menu déroulant éditable
|
- [x] Transformer la cellule Ventilation en menu déroulant éditable
|
||||||
- [x] Sauvegarder automatiquement les modifications (mutation updateFieldMutation)
|
- [x] Sauvegarder automatiquement les modifications (mutation updateFieldMutation)
|
||||||
- [x] Afficher une notification de succès après sauvegarde
|
- [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