From 4089037fb7a7bfcfefb5e0045c2a4cc0ef506bb9 Mon Sep 17 00:00:00 2001 From: LeonG11 Date: Sat, 27 Jun 2026 21:52:03 +0300 Subject: [PATCH] feat: add form stock in stock out on front --- frontend/src/components/AdminItems.jsx | 3 +- frontend/src/components/AdminStock.jsx | 287 ++++++++++++++++++++----- 2 files changed, 232 insertions(+), 58 deletions(-) diff --git a/frontend/src/components/AdminItems.jsx b/frontend/src/components/AdminItems.jsx index 7af299e..01447fb 100644 --- a/frontend/src/components/AdminItems.jsx +++ b/frontend/src/components/AdminItems.jsx @@ -116,7 +116,6 @@ function ItemFormModal({ item, onClose, onSaved }) { placeholder="Остаток" value={form.stock} onChange={handleChange("stock")} - disabled /> {saving ? "Сохранение..." : isEdit ? "Сохранить" : "Создать"} diff --git a/frontend/src/components/AdminStock.jsx b/frontend/src/components/AdminStock.jsx index 780689a..57594d9 100644 --- a/frontend/src/components/AdminStock.jsx +++ b/frontend/src/components/AdminStock.jsx @@ -1,5 +1,10 @@ import { useState, useEffect } from "react"; -import { getStockMovements } from "../api/index"; +import { + getStockMovements, + stockIn, + stockOut, + getAllItems, +} from "../api/index"; const REASON_LABELS = { purchase: "Закупка", @@ -10,75 +15,245 @@ const REASON_LABELS = { export default function AdminStock() { const [movements, setMovements] = useState([]); + const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); - const loadMovements = () => { - getStockMovements() - .then((res) => { - setMovements(res.data); + // состояние форм + const [inForm, setInForm] = useState({ + item_id: "", + quantity: "", + comment: "", + }); + const [outForm, setOutForm] = useState({ + item_id: "", + quantity: "", + comment: "", + }); + const [busy, setBusy] = useState(false); + + const loadAll = () => { + Promise.all([getStockMovements(), getAllItems()]) + .then(([movRes, itemsRes]) => { + setMovements(movRes.data); + setItems(itemsRes.data); }) .catch(() => { }) .finally(() => setLoading(false)); }; useEffect(() => { - loadMovements(); + loadAll(); }, []); - if (loading) return

Загрузка...

; + // приход + const handleStockIn = async () => { + if (!inForm.item_id || !inForm.quantity || Number(inForm.quantity) <= 0) { + alert("Выберите товар и укажите количество"); + return; + } + setBusy(true); + try { + await stockIn({ + item_id: Number(inForm.item_id), + quantity: Number(inForm.quantity), + comment: inForm.comment, + }); + setInForm({ item_id: "", quantity: "", comment: "" }); + loadAll(); // обновить историю и остатки + } catch { + alert("Не удалось оформить приход"); + } finally { + setBusy(false); + } + }; + + // уход + const handleStockOut = async () => { + if ( + !outForm.item_id || + !outForm.quantity || + Number(outForm.quantity) <= 0 + ) { + alert("Выберите товар и укажите количество"); + return; + } + setBusy(true); + try { + await stockOut({ + item_id: Number(outForm.item_id), + quantity: Number(outForm.quantity), + comment: outForm.comment, + }); + setOutForm({ item_id: "", quantity: "", comment: "" }); + loadAll(); + } catch (err) { + // бэк возвращает "недостаточно товара" при нехватке + alert(err.response?.data?.error || "Не удалось оформить списание"); + } finally { + setBusy(false); + } + }; + + if (loading) return

Загрузка...

; + + // общий класс инпутов + const inputClass = + "bg-white/5 border border-white/10 rounded px-3 py-2 text-white placeholder-white/40 focus:border-gold focus:outline-none"; return ( -
-

История движения

- {movements.length === 0 ? ( -

Движений нет

- ) : ( -
- - - - - - - - - - - - - {movements.map((m) => ( - - - - - - - - +
+ {/* === Формы === */} +
+ {/* Приход */} +
+

+ Приход товара +

+
+
-
ДатаТоварТипКоличествоПричинаКомментарий
- {new Date(m.created_at).toLocaleDateString("ru-RU")} - {m.item?.name} - - {m.type === "in" ? "Приход" : "Уход"} - - - {m.quantity} {m.item?.unit} - - {REASON_LABELS[m.reason] || m.reason} - - {m.comment || "-"} -
+ + + setInForm({ ...inForm, quantity: e.target.value }) + } + /> + + setInForm({ ...inForm, comment: e.target.value }) + } + /> + +
- )} + + {/* Уход */} +
+

+ Списание товара +

+
+ + + setOutForm({ ...outForm, quantity: e.target.value }) + } + /> + + setOutForm({ ...outForm, comment: e.target.value }) + } + /> + +
+
+ + + {/* === История (твоя таблица) === */} +
+

+ История движений +

+ {movements.length === 0 ? ( +

Движений нет

+ ) : ( +
+ + + + + + + + + + + + + {movements.map((m) => ( + + + + + + + + + ))} + +
ДатаТоварТипКоличествоПричинаКомментарий
+ {new Date(m.created_at).toLocaleDateString("ru-RU")} + {m.item?.name} + + {m.type === "in" ? "Приход" : "Уход"} + + + {m.quantity} {m.item?.unit} + + {REASON_LABELS[m.reason] || m.reason} + + {m.comment || "—"} +
+
+ )} +
); }