From b090c8f36ac4a01dd24503ec2bc19273dc5b2bc4 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Thu, 2 Apr 2026 09:20:54 +0200 Subject: [PATCH] feat: implement DLC management page with EAN lookup, filtering, and CRUD operations --- client/src/pages/DlcPage.tsx | 65 +++++++++++++++++++++++++---- client/src/pages/mobile/DlcPage.tsx | 59 ++++++++++++++++++++++++-- 2 files changed, 114 insertions(+), 10 deletions(-) diff --git a/client/src/pages/DlcPage.tsx b/client/src/pages/DlcPage.tsx index 6df2f66..99e524a 100644 --- a/client/src/pages/DlcPage.tsx +++ b/client/src/pages/DlcPage.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useMemo } from "react"; +import { useState, useEffect, useMemo, useRef } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; @@ -12,7 +12,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Textarea } from "@/components/ui/textarea"; -import { AlertTriangle, Plus, Eye, Edit, Trash2, CheckCircle, CheckCircle2, Package, Clock, AlertCircle, Filter, Download, FileText, PackageX, RotateCcw, X } from "lucide-react"; +import { AlertTriangle, Plus, Eye, Edit, Trash2, CheckCircle, CheckCircle2, Package, Clock, AlertCircle, Filter, Download, FileText, PackageX, RotateCcw, X, Loader2 } from "lucide-react"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -115,6 +115,51 @@ export default function DlcPage() { }, }); + // EAN lookup from external API + const [eanLookupLoading, setEanLookupLoading] = useState(false); + const eanDebounceRef = useRef | null>(null); + const gencodeValue = form.watch("gencode"); + + useEffect(() => { + if (!gencodeValue || gencodeValue.length < 8) return; + + if (eanDebounceRef.current) clearTimeout(eanDebounceRef.current); + + eanDebounceRef.current = setTimeout(async () => { + setEanLookupLoading(true); + try { + const res = await fetch(`https://api.ffnancy.fr/api/articles?ean=${encodeURIComponent(gencodeValue)}&limit=1`); + if (!res.ok) return; + const data = await res.json(); + const article = data.articles?.[0]; + if (!article) return; + + // Toujours mettre à jour le nom du produit + form.setValue("productName", article.libelle1, { shouldValidate: true }); + + // Matcher le fournisseur par nom + if (article.nom_fou_principal) { + const apiName = article.nom_fou_principal.toLowerCase().trim(); + const matched = (suppliers as any[]).find((s: any) => + s.name.toLowerCase().trim().includes(apiName) || + apiName.includes(s.name.toLowerCase().trim()) + ); + if (matched) { + form.setValue("supplierId", matched.id, { shouldValidate: true }); + } + } + } catch { + // Lookup best-effort, on ignore les erreurs + } finally { + setEanLookupLoading(false); + } + }, 600); + + return () => { + if (eanDebounceRef.current) clearTimeout(eanDebounceRef.current); + }; + }, [gencodeValue, suppliers]); + // Create mutation - optimized cache invalidation const createMutation = useMutation({ mutationFn: (data: InsertDlcProduct) => apiRequest("/api/dlc-products", "POST", data), @@ -670,11 +715,17 @@ export default function DlcPage() { Code EAN13 (optionnel) - +
+ + {eanLookupLoading && ( + + )} +
diff --git a/client/src/pages/mobile/DlcPage.tsx b/client/src/pages/mobile/DlcPage.tsx index 34802a2..c6dabc3 100644 --- a/client/src/pages/mobile/DlcPage.tsx +++ b/client/src/pages/mobile/DlcPage.tsx @@ -2,7 +2,7 @@ * MobileDlcPage.tsx * Version mobile de la page Gestion des DLC */ -import { useState, useMemo } from "react"; +import { useState, useMemo, useEffect, useRef } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useAuthUnified } from "@/hooks/useAuthUnified"; import { useStore } from "@/contexts/StoreContext"; @@ -20,7 +20,8 @@ import { CheckCircle, X, Calendar, - MoreVertical + MoreVertical, + Loader2 } from "lucide-react"; import { format, parseISO } from "date-fns"; import { fr } from "date-fns/locale"; @@ -139,6 +140,49 @@ export default function MobileDlcPage() { } }); + // EAN lookup from external API + const [eanLookupLoading, setEanLookupLoading] = useState(false); + const eanDebounceRef = useRef | null>(null); + const gencodeValue = form.watch("gencode"); + + useEffect(() => { + if (!gencodeValue || gencodeValue.length < 8) return; + + if (eanDebounceRef.current) clearTimeout(eanDebounceRef.current); + + eanDebounceRef.current = setTimeout(async () => { + setEanLookupLoading(true); + try { + const res = await fetch(`https://api.ffnancy.fr/api/articles?ean=${encodeURIComponent(gencodeValue)}&limit=1`); + if (!res.ok) return; + const data = await res.json(); + const article = data.articles?.[0]; + if (!article) return; + + form.setValue("productName", article.libelle1, { shouldValidate: true }); + + if (article.nom_fou_principal) { + const apiName = article.nom_fou_principal.toLowerCase().trim(); + const matched = (suppliers as any[]).find((s: any) => + s.name.toLowerCase().trim().includes(apiName) || + apiName.includes(s.name.toLowerCase().trim()) + ); + if (matched) { + form.setValue("supplierId", matched.id, { shouldValidate: true }); + } + } + } catch { + // Lookup best-effort, on ignore les erreurs + } finally { + setEanLookupLoading(false); + } + }, 600); + + return () => { + if (eanDebounceRef.current) clearTimeout(eanDebounceRef.current); + }; + }, [gencodeValue, suppliers]); + const onSubmit = (data: any) => { let groupId = selectedStoreId; if (!groupId && user?.userGroups?.[0]?.groupId) groupId = user.userGroups[0].groupId; @@ -386,7 +430,16 @@ export default function MobileDlcPage() { Gencode (Scanner si dispo) - +
+ + {eanLookupLoading && ( + + )} +
)}