fix: change backend hadlers, API route and uploads photo

This commit is contained in:
2026-06-24 20:47:14 +03:00
parent 2c22cee60b
commit 115f52a8f1
5 changed files with 189 additions and 10 deletions
+10 -2
View File
@@ -42,11 +42,19 @@ export const getAllCategories = () => axios.get(`${API_URL}/getCategories`);
export const getCart = () => axios.get(`${API_URL}/cart/`);
export const addToCart = (data) => axios.post(`${API_URL}/cart/`, data);
export const updateCartItem = (id, data) =>
axios.post(`${API_URL}/cart/${id}`, data);
axios.patch(`${API_URL}/cart/${id}`, data);
export const removeFromCart = (id) => axios.delete(`${API_URL}/cart/${id}`);
export const createOrder = (data) => axios.post(`${API_URL}/orders/`, data);
export const getUserOrders = () => axios.get(`${API_URL}/orders`);
export const getOrder = (id) => axios.get(`${API_URL}/orders/${id}`);
export const updateProfile = () => axios.patch(`${API_URL}/profile`);
export const updateProfile = (data) => axios.patch(`${API_URL}/profile`);
export const getUserInfo = () => axios.get(`${API_URL}/profile`);
export const uploadItemAvatar = (id, file) => {
const formData = new FormData();
formData.append("avatar", file);
return axios.patch(`${API_URL}/admin/items/${id}/avatar`, formData, {
headers: { "Content-Type": "multipart/form-data" },
});
};
+173 -5
View File
@@ -1,9 +1,152 @@
import { useState, useEffect } from "react";
import { getAllItems, deleteItem } from "../api/index";
import {
getAllItems,
deleteItem,
addItem,
updateItem,
uploadItemAvatar,
} from "../api/index";
function ItemFormModal({ item, onClose, onSaved }) {
const isEdit = !!item;
const [form, setForm] = useState({
name: item?.name || "",
category: item?.category || "",
city: item?.city || "",
price: item?.price || "",
stock: item?.stock || "",
unit: item?.unit || "кг",
unit_type: item?.unit_type || "weight",
});
const [file, setFile] = useState(null);
const [saving, setSaving] = useState(false);
const handleChange = (field) => (e) =>
setForm({ ...form, [field]: e.target.value });
const handleSubmit = async () => {
setSaving(true);
try {
// числовые поля привести к числам
const payload = {
...form,
price: Number(form.price),
stock: Number(form.stock),
};
let itemId;
if (isEdit) {
await updateItem(item.id, payload);
itemId = item.id;
} else {
const res = await addItem(payload);
itemId = res.data.id;
}
// если выбран файл — загрузить картинку
if (file) {
await uploadItemAvatar(itemId, file);
}
onSaved();
} catch {
alert("Не удалось сохранить товар");
} finally {
setSaving(false);
}
};
return (
<div className="fixed inset-0 bg-black/70 flex items-center justify-center z-50 p-4">
<div className="bg-slate-800 rounded-lg p-6 w-full max-w-md max-h-[90vh] overflow-y-auto text-white">
<h2 className="text-xl font-medium mb-4">
{isEdit ? "Редактировать товар" : "Новый товар"}
</h2>
<div className="flex flex-col gap-3">
<input
className="bg-white/10 rounded px-3 py-2"
placeholder="Название"
value={form.name}
onChange={handleChange("name")}
/>
<input
className="bg-white/10 rounded px-3 py-2"
placeholder="Категория"
value={form.category}
onChange={handleChange("category")}
/>
<input
className="bg-white/10 rounded px-3 py-2"
placeholder="Город"
value={form.city}
onChange={handleChange("city")}
/>
<input
type="number"
className="bg-white/10 rounded px-3 py-2"
placeholder="Цена"
value={form.price}
onChange={handleChange("price")}
/>
<input
type="number"
className="bg-white/10 rounded px-3 py-2"
placeholder="Остаток"
value={form.stock}
onChange={handleChange("stock")}
/>
<input
className="bg-white/10 rounded px-3 py-2"
placeholder="Единица (кг, шт)"
value={form.unit}
onChange={handleChange("unit")}
/>
<select
className="bg-white/10 rounded px-3 py-2"
value={form.unit_type}
onChange={handleChange("unit_type")}
>
<option value="weight">На вес</option>
<option value="piece">Поштучно</option>
</select>
{/* картинка */}
<div>
<label className="text-white/50 text-sm block mb-1">Картинка</label>
<input
type="file"
accept="image/*"
onChange={(e) => setFile(e.target.files[0])}
className="text-sm text-white/70"
/>
</div>
</div>
<div className="flex gap-3 mt-6">
<button
onClick={handleSubmit}
disabled={saving}
className="bg-gold text-black px-6 py-2 rounded font-medium flex-grow disabled:opacity-50"
>
{saving ? "Сохранение..." : isEdit ? "Сохранить" : "Создать"}
</button>
<button onClick={onClose} className="bg-white/10 px-6 py-2 rounded">
Отмена
</button>
</div>
</div>
</div>
);
}
// ===== Основной компонент =====
export default function AdminItems() {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(true);
const [showModal, setShowModal] = useState(false);
const [editingItem, setEditingItem] = useState(null);
const loadItems = () => {
getAllItems()
@@ -26,16 +169,30 @@ export default function AdminItems() {
}
};
const openCreate = () => {
setEditingItem(null);
setShowModal(true);
};
const openEdit = (item) => {
setEditingItem(item);
setShowModal(true);
};
const handleSaved = () => {
setShowModal(false);
loadItems();
};
if (loading) return <p className="text-white/60">Загрузка...</p>;
return (
<div>
{/* Кнопка добавления (форму подключим следующим шагом) */}
<button className="mb-6 bg-gold text-black px-6 py-2 rounded-md font-medium uppercase text-sm">
<button
onClick={openCreate}
className="mb-6 bg-gold text-black px-6 py-2 rounded-md font-medium uppercase text-sm"
>
+ Добавить товар
</button>
{/* Таблица товаров */}
<div className="overflow-x-auto">
<table className="w-full text-sm text-left">
<thead className="text-white/50 uppercase text-xs border-b border-white/10">
@@ -73,7 +230,10 @@ export default function AdminItems() {
</td>
<td className="py-3 pr-4">
<div className="flex gap-2">
<button className="text-blue-400 hover:text-blue-300 text-sm">
<button
onClick={() => openEdit(item)}
className="text-blue-400 hover:text-blue-300 text-sm"
>
Изменить
</button>
<button
@@ -89,6 +249,14 @@ export default function AdminItems() {
</tbody>
</table>
</div>
{showModal && (
<ItemFormModal
item={editingItem}
onClose={() => setShowModal(false)}
onSaved={handleSaved}
/>
)}
</div>
);
}