import React, { useState, useEffect, useMemo } from 'react'; import { Plus, Minus, Package, ShoppingCart, Download, Trash2, History, TrendingUp, AlertTriangle, ClipboardCheck, Search, ChevronDown, ChevronUp } from 'lucide-react'; import { initializeApp } from 'firebase/app'; import { getFirestore, collection, doc, setDoc, onSnapshot, addDoc, updateDoc, deleteDoc, query, serverTimestamp } from 'firebase/firestore'; import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from 'firebase/auth'; // --- Firebase Configuration --- const firebaseConfig = JSON.parse(__firebase_config); const app = initializeApp(firebaseConfig); const auth = getAuth(app); const db = getFirestore(app); const appId = typeof __app_id !== 'undefined' ? __app_id : 'sneaker-inventory-app'; export default function App() { const [user, setUser] = useState(null); const [sneakers, setSneakers] = useState([]); const [transactions, setTransactions] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); // Form states const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [isSaleModalOpen, setIsSaleModalOpen] = useState(null); // stores sneaker object const [newSneaker, setNewSneaker] = useState({ model: '', brand: '', size: '', price: '', stock: '' }); const [saleQuantity, setSaleQuantity] = useState(1); const [view, setView] = useState('inventory'); // 'inventory' | 'history' // 1. Auth & Initial Load useEffect(() => { const initAuth = async () => { try { if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) { await signInWithCustomToken(auth, __initial_auth_token); } else { await signInAnonymously(auth); } } catch (err) { console.error("Auth error:", err); } }; initAuth(); const unsubscribeAuth = onAuthStateChanged(auth, (user) => { setUser(user); if (!user) setLoading(false); }); return () => unsubscribeAuth(); }, []); // 2. Data Listeners useEffect(() => { if (!user) return; const sneakersCol = collection(db, 'artifacts', appId, 'public', 'data', 'sneakers'); const historyCol = collection(db, 'artifacts', appId, 'public', 'data', 'transactions'); const unsubSneakers = onSnapshot(sneakersCol, (snapshot) => { const data = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); setSneakers(data); setLoading(false); }, (err) => console.error("Firestore error:", err) ); const unsubHistory = onSnapshot(historyCol, (snapshot) => { const data = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); // Sort in memory by timestamp descending data.sort((a, b) => (b.timestamp?.seconds || 0) - (a.timestamp?.seconds || 0)); setTransactions(data); }, (err) => console.error("Firestore history error:", err) ); return () => { unsubSneakers(); unsubHistory(); }; }, [user]); // Actions const handleAddSneaker = async (e) => { e.preventDefault(); if (!newSneaker.model || !newSneaker.price || !newSneaker.stock) return; try { const sneakersCol = collection(db, 'artifacts', appId, 'public', 'data', 'sneakers'); await addDoc(sneakersCol, { ...newSneaker, price: Number(newSneaker.price), stock: Number(newSneaker.stock), createdAt: serverTimestamp() }); setNewSneaker({ model: '', brand: '', size: '', price: '', stock: '' }); setIsAddModalOpen(false); } catch (err) { console.error(err); } }; const handleSale = async () => { if (!isSaleModalOpen || saleQuantity <= 0) return; const sneaker = isSaleModalOpen; if (sneaker.stock < saleQuantity) { alert("Недостаточно товара на складе!"); return; } try { const sneakerRef = doc(db, 'artifacts', appId, 'public', 'data', 'sneakers', sneaker.id); const historyCol = collection(db, 'artifacts', appId, 'public', 'data', 'transactions'); // Update Stock await updateDoc(sneakerRef, { stock: sneaker.stock - saleQuantity }); // Log Transaction await addDoc(historyCol, { sneakerId: sneaker.id, model: sneaker.model, brand: sneaker.brand, size: sneaker.size, quantity: saleQuantity, totalPrice: sneaker.price * saleQuantity, type: 'sale', timestamp: serverTimestamp() }); setIsSaleModalOpen(null); setSaleQuantity(1); } catch (err) { console.error(err); } }; const handleDelete = async (id) => { if (!confirm("Удалить этот товар из базы?")) return; try { await deleteDoc(doc(db, 'artifacts', appId, 'public', 'data', 'sneakers', id)); } catch (err) { console.error(err); } }; // Export to CSV Function const exportToCSV = () => { const headers = ["Модель", "Бренд", "Размер", "Цена", "Остаток"]; const rows = sneakers.map(s => [s.model, s.brand, s.size, s.price, s.stock]); let csvContent = "\uFEFF"; // UTF-8 BOM for Excel csvContent += headers.join(",") + "\n"; rows.forEach(row => { csvContent += row.join(",") + "\n"; }); const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement("a"); const url = URL.createObjectURL(blob); link.setAttribute("href", url); link.setAttribute("download", `sneakers_stock_${new Date().toISOString().split('T')[0]}.csv`); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; // Copy for Google Sheets (Tab-separated) const copyForSheets = () => { const headers = "Модель\tБренд\tРазмер\tЦена\tОстаток"; const rows = sneakers.map(s => `${s.model}\t${s.brand}\t${s.size}\t${s.price}\t${s.stock}`).join("\n"); const fullText = headers + "\n" + rows; const textarea = document.createElement('textarea'); textarea.value = fullText; document.body.appendChild(textarea); textarea.select(); document.execCommand('copy'); document.body.removeChild(textarea); alert("Данные скопированы! Просто вставьте их (Ctrl+V) в Google Таблицу."); }; const filteredSneakers = useMemo(() => { return sneakers.filter(s => s.model.toLowerCase().includes(searchTerm.toLowerCase()) || s.brand.toLowerCase().includes(searchTerm.toLowerCase()) ); }, [sneakers, searchTerm]); const stats = useMemo(() => { return { totalItems: sneakers.reduce((acc, curr) => acc + Number(curr.stock), 0), totalValue: sneakers.reduce((acc, curr) => acc + (Number(curr.stock) * Number(curr.price)), 0), lowStock: sneakers.filter(s => s.stock <= 3).length }; }, [sneakers]); if (loading) { return (
Система учета кроссовок v1.0
Всего пар
{stats.totalItems}
Оценка склада
{stats.totalValue.toLocaleString()} ₽
Мало остатков
{stats.lowStock}
| Модель | Бренд | Размер | Цена | Остаток | Действия |
|---|---|---|---|---|---|
| {item.model} | {item.brand} | {item.size} | {item.price.toLocaleString()} ₽ | {item.stock} | |
| Ничего не найдено | |||||
| Дата | Товар | Кол-во | Сумма |
|---|---|---|---|
| {t.timestamp?.toDate ? t.timestamp.toDate().toLocaleString() : 'Обработка...'} | {t.brand} {t.model} (р.{t.size}) | -{t.quantity} | +{t.totalPrice.toLocaleString()} ₽ |
| Продаж пока не было | |||
{isSaleModalOpen.brand} {isSaleModalOpen.model}
Размер: {isSaleModalOpen.size} | На складе: {isSaleModalOpen.stock}