mirror of
https://github.com/R0m1k3/LogiFlow.git
synced 2026-10-23 23:18:18 +02:00
feat: add CustomerOrderForm component with automated article lookup and supplier matching
This commit is contained in:
1 parent
58dde2f785
commit
6df51a3934
2 files changed
+36
-27
No files matched your search
@@ -158,19 +158,22 @@ export function CustomerOrderForm({
|
|||||||
|
|
||||||
// Lookup API ffnancy par gencode ou référence
|
// Lookup API ffnancy par gencode ou référence
|
||||||
const [articleLookupLoading, setArticleLookupLoading] = useState(false);
|
const [articleLookupLoading, setArticleLookupLoading] = useState(false);
|
||||||
|
const [articleNotFound, setArticleNotFound] = useState(false);
|
||||||
const lookupDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const lookupDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
const gencodeValue = form.watch("gencode");
|
const gencodeValue = form.watch("gencode");
|
||||||
const referenceValue = form.watch("productReference");
|
const referenceValue = form.watch("productReference");
|
||||||
|
|
||||||
const fetchAndFillArticle = async (params: URLSearchParams) => {
|
const fetchAndFillArticle = async (params: URLSearchParams) => {
|
||||||
setArticleLookupLoading(true);
|
setArticleLookupLoading(true);
|
||||||
|
setArticleNotFound(false);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/ffnancy/articles?${params}&limit=1`, { credentials: 'include' });
|
const res = await fetch(`/api/ffnancy/articles?${params}&limit=1`, { credentials: 'include' });
|
||||||
if (!res.ok) return;
|
if (!res.ok) { setArticleNotFound(true); return; }
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const article = data.articles?.[0];
|
const article = data.articles?.[0];
|
||||||
if (!article) return;
|
if (!article) { setArticleNotFound(true); return; }
|
||||||
|
|
||||||
|
setArticleNotFound(false);
|
||||||
form.setValue("productName", article.libelle1, { shouldValidate: true });
|
form.setValue("productName", article.libelle1, { shouldValidate: true });
|
||||||
if (article.gtin) form.setValue("gencode", article.gtin, { shouldValidate: true });
|
if (article.gtin) form.setValue("gencode", article.gtin, { shouldValidate: true });
|
||||||
if (article.codein) form.setValue("productReference", article.codein, { shouldValidate: true });
|
if (article.codein) form.setValue("productReference", article.codein, { shouldValidate: true });
|
||||||
@@ -198,27 +201,25 @@ export function CustomerOrderForm({
|
|||||||
);
|
);
|
||||||
if (matched) form.setValue("supplierId", matched.id, { shouldValidate: true });
|
if (matched) form.setValue("supplierId", matched.id, { shouldValidate: true });
|
||||||
}
|
}
|
||||||
} catch { /* best-effort */ } finally {
|
} catch { setArticleNotFound(true); } finally {
|
||||||
setArticleLookupLoading(false);
|
setArticleLookupLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!gencodeValue || gencodeValue.length < 8) return;
|
if (!gencodeValue || gencodeValue.length < 8) { setArticleNotFound(false); return; }
|
||||||
if (lookupDebounceRef.current) clearTimeout(lookupDebounceRef.current);
|
if (lookupDebounceRef.current) clearTimeout(lookupDebounceRef.current);
|
||||||
lookupDebounceRef.current = setTimeout(() => {
|
lookupDebounceRef.current = setTimeout(() => {
|
||||||
const p = new URLSearchParams({ ean: gencodeValue });
|
fetchAndFillArticle(new URLSearchParams({ ean: gencodeValue }));
|
||||||
fetchAndFillArticle(p);
|
|
||||||
}, 600);
|
}, 600);
|
||||||
return () => { if (lookupDebounceRef.current) clearTimeout(lookupDebounceRef.current); };
|
return () => { if (lookupDebounceRef.current) clearTimeout(lookupDebounceRef.current); };
|
||||||
}, [gencodeValue]);
|
}, [gencodeValue]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!referenceValue || referenceValue.length < 3) return;
|
if (!referenceValue || referenceValue.length < 3) { setArticleNotFound(false); return; }
|
||||||
if (lookupDebounceRef.current) clearTimeout(lookupDebounceRef.current);
|
if (lookupDebounceRef.current) clearTimeout(lookupDebounceRef.current);
|
||||||
lookupDebounceRef.current = setTimeout(() => {
|
lookupDebounceRef.current = setTimeout(() => {
|
||||||
const p = new URLSearchParams({ codein: referenceValue });
|
fetchAndFillArticle(new URLSearchParams({ codein: referenceValue }));
|
||||||
fetchAndFillArticle(p);
|
|
||||||
}, 600);
|
}, 600);
|
||||||
return () => { if (lookupDebounceRef.current) clearTimeout(lookupDebounceRef.current); };
|
return () => { if (lookupDebounceRef.current) clearTimeout(lookupDebounceRef.current); };
|
||||||
}, [referenceValue]);
|
}, [referenceValue]);
|
||||||
@@ -425,26 +426,26 @@ export function CustomerOrderForm({
|
|||||||
{/* Case "Client appelé" cachée pour nouvelles commandes */}
|
{/* Case "Client appelé" cachée pour nouvelles commandes */}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Produit non référencé */}
|
||||||
|
{articleNotFound && (
|
||||||
|
<div className="flex items-center gap-2 rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700">
|
||||||
|
<span className="font-medium">Produit non référencé</span> — ce produit n'existe pas dans la base. La commande ne peut pas être créée.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Actions */}
|
{/* Actions */}
|
||||||
<div className="flex justify-end space-x-3 pt-4">
|
<div className="flex justify-end space-x-3 pt-4">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
Annuler
|
Annuler
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isLoading}
|
disabled={isLoading || articleNotFound || articleLookupLoading}
|
||||||
onClick={(e) => {
|
|
||||||
console.log("Submit button clicked");
|
|
||||||
console.log("Form state:", form.formState);
|
|
||||||
console.log("Form values:", form.getValues());
|
|
||||||
console.log("Form validation errors:", form.formState.errors);
|
|
||||||
console.log("Current user context:", user?.userGroups, "available groups:", groups);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{isLoading ? "Enregistrement..." : order ? "Modifier" : "Créer"}
|
{isLoading ? "Enregistrement..." : order ? "Modifier" : "Créer"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -160,19 +160,22 @@ export default function MobileCustomerOrdersPage() {
|
|||||||
|
|
||||||
// Lookup API ffnancy par gencode ou référence
|
// Lookup API ffnancy par gencode ou référence
|
||||||
const [articleLookupLoading, setArticleLookupLoading] = useState(false);
|
const [articleLookupLoading, setArticleLookupLoading] = useState(false);
|
||||||
|
const [articleNotFound, setArticleNotFound] = useState(false);
|
||||||
const lookupDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
const lookupDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
const gencodeValue = form.watch("gencode");
|
const gencodeValue = form.watch("gencode");
|
||||||
const referenceValue = form.watch("productReference");
|
const referenceValue = form.watch("productReference");
|
||||||
|
|
||||||
const fetchAndFillArticle = async (params: URLSearchParams) => {
|
const fetchAndFillArticle = async (params: URLSearchParams) => {
|
||||||
setArticleLookupLoading(true);
|
setArticleLookupLoading(true);
|
||||||
|
setArticleNotFound(false);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/ffnancy/articles?${params}&limit=1`, { credentials: 'include' });
|
const res = await fetch(`/api/ffnancy/articles?${params}&limit=1`, { credentials: 'include' });
|
||||||
if (!res.ok) return;
|
if (!res.ok) { setArticleNotFound(true); return; }
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const article = data.articles?.[0];
|
const article = data.articles?.[0];
|
||||||
if (!article) return;
|
if (!article) { setArticleNotFound(true); return; }
|
||||||
|
|
||||||
|
setArticleNotFound(false);
|
||||||
form.setValue("productName", article.libelle1, { shouldValidate: true });
|
form.setValue("productName", article.libelle1, { shouldValidate: true });
|
||||||
if (article.gtin) form.setValue("gencode", article.gtin, { shouldValidate: true });
|
if (article.gtin) form.setValue("gencode", article.gtin, { shouldValidate: true });
|
||||||
if (article.codein) form.setValue("productReference", article.codein, { shouldValidate: true });
|
if (article.codein) form.setValue("productReference", article.codein, { shouldValidate: true });
|
||||||
@@ -199,13 +202,13 @@ export default function MobileCustomerOrdersPage() {
|
|||||||
);
|
);
|
||||||
if (matched) form.setValue("supplierId", matched.id, { shouldValidate: true });
|
if (matched) form.setValue("supplierId", matched.id, { shouldValidate: true });
|
||||||
}
|
}
|
||||||
} catch { /* best-effort */ } finally {
|
} catch { setArticleNotFound(true); } finally {
|
||||||
setArticleLookupLoading(false);
|
setArticleLookupLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!gencodeValue || gencodeValue.length < 8) return;
|
if (!gencodeValue || gencodeValue.length < 8) { setArticleNotFound(false); return; }
|
||||||
if (lookupDebounceRef.current) clearTimeout(lookupDebounceRef.current);
|
if (lookupDebounceRef.current) clearTimeout(lookupDebounceRef.current);
|
||||||
lookupDebounceRef.current = setTimeout(() => {
|
lookupDebounceRef.current = setTimeout(() => {
|
||||||
fetchAndFillArticle(new URLSearchParams({ ean: gencodeValue }));
|
fetchAndFillArticle(new URLSearchParams({ ean: gencodeValue }));
|
||||||
@@ -214,7 +217,7 @@ export default function MobileCustomerOrdersPage() {
|
|||||||
}, [gencodeValue]);
|
}, [gencodeValue]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!referenceValue || referenceValue.length < 3) return;
|
if (!referenceValue || referenceValue.length < 3) { setArticleNotFound(false); return; }
|
||||||
if (lookupDebounceRef.current) clearTimeout(lookupDebounceRef.current);
|
if (lookupDebounceRef.current) clearTimeout(lookupDebounceRef.current);
|
||||||
lookupDebounceRef.current = setTimeout(() => {
|
lookupDebounceRef.current = setTimeout(() => {
|
||||||
fetchAndFillArticle(new URLSearchParams({ codein: referenceValue }));
|
fetchAndFillArticle(new URLSearchParams({ codein: referenceValue }));
|
||||||
@@ -575,7 +578,12 @@ export default function MobileCustomerOrdersPage() {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button type="submit" className="w-full bg-blue-600 h-12 text-lg font-bold shadow-lg mt-4" disabled={createMutation.isPending}>
|
{articleNotFound && (
|
||||||
|
<div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700">
|
||||||
|
<span className="font-medium">Produit non référencé</span> — ce produit n'existe pas dans la base. La commande ne peut pas être créée.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Button type="submit" className="w-full bg-blue-600 h-12 text-lg font-bold shadow-lg mt-4" disabled={createMutation.isPending || articleNotFound || articleLookupLoading}>
|
||||||
{createMutation.isPending ? "Création..." : "Valider la commande"}
|
{createMutation.isPending ? "Création..." : "Valider la commande"}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in new issue
Block a user