import React, { useState, useMemo } from 'react'; import { Plus, Trash2, Save, DollarSign, CreditCard, TrendingDown, Users, BarChart3, Calendar, Calculator } from 'lucide-react'; // --- Компоненты UI --- const Card = ({ children, className = "" }) => (
{children}
); const SectionHeader = ({ icon: Icon, title }) => (

{title}

); const InputGroup = ({ label, value, onChange, type = "number", prefix = "" }) => (
{prefix && (
{prefix}
)} onChange(type === "number" ? parseFloat(e.target.value) || 0 : e.target.value)} />
); // --- Основное Приложение --- export default function FlowerShopFinance() { const [activeTab, setActiveTab] = useState('weekly'); // --- Состояние данных (State) --- // Пример данных за одну неделю const [weeks, setWeeks] = useState([ { id: 1, name: "01.11 – 07.11", month: "Ноябрь 2025", sales: { cash: 150000, card: 210000, credit: 30000 // Новые продажи в долг }, debtPayments: { cash: 10000, card: 15000 }, supply: { invoiceTotal: 120000, // Закуп по инвойсу writeOffSupplier: 5000 // Брак поставщика }, storeWriteOff: 8000, // Списание магазина (увядание/реклама) stockBalance: 300000, // Остаток на конец недели expenses: [ { id: 1, category: "Постоянный", amount: 20000, method: "Безнал", note: "Аренда часть" }, { id: 2, category: "Разовый", amount: 5000, method: "Наличные", note: "Вода, чай" }, { id: 3, category: "Возврат_поставщику", amount: 0, method: "Безнал", note: "" } ] } ]); const [debtors, setDebtors] = useState([ { id: 1, date: "2025-11-01", client: "Ресторан 'Мята'", change: 30000, comment: "Заказ банкет" }, { id: 2, date: "2025-11-03", client: "Ресторан 'Мята'", change: -10000, comment: "Оплата частичная (нал)" } ]); const [currentWeekId, setCurrentWeekId] = useState(1); // --- Вычисления (Хуки) --- const currentWeek = weeks.find(w => w.id === currentWeekId); // Функции для обновления текущей недели const updateWeekVal = (field, subfield, value) => { setWeeks(weeks.map(w => { if (w.id !== currentWeekId) return w; if (subfield) { return { ...w, [field]: { ...w[field], [subfield]: value } }; } return { ...w, [field]: value }; })); }; const addExpense = () => { const newExp = { id: Date.now(), category: "Разовый", amount: 0, method: "Наличные", note: "" }; setWeeks(weeks.map(w => w.id === currentWeekId ? { ...w, expenses: [...w.expenses, newExp] } : w)); }; const updateExpense = (expId, field, value) => { setWeeks(weeks.map(w => { if (w.id !== currentWeekId) return w; const newExpenses = w.expenses.map(e => e.id === expId ? { ...e, [field]: value } : e); return { ...w, expenses: newExpenses }; })); }; const removeExpense = (expId) => { setWeeks(weeks.map(w => w.id === currentWeekId ? { ...w, expenses: w.expenses.filter(e => e.id !== expId) } : w)); }; // --- Расчеты по текущей неделе (аналог СУММЕСЛИ) --- const sumExpenses = (category, method = null) => { return currentWeek.expenses .filter(e => e.category === category && (method ? e.method === method : true)) .reduce((sum, e) => sum + e.amount, 0); }; const weeklyStats = useMemo(() => { if (!currentWeek) return {}; const revenueFact = currentWeek.sales.cash + currentWeek.sales.card + currentWeek.debtPayments.cash + currentWeek.debtPayments.card; const expenseConstant = sumExpenses("Постоянный"); const expenseOneTime = sumExpenses("Разовый"); const supplierReturn = sumExpenses("Возврат_поставщику"); // Расходы по типам оплаты для кассы const expenseCash = currentWeek.expenses.filter(e => e.method === "Наличные").reduce((a, b) => a + b.amount, 0); const expenseCard = currentWeek.expenses.filter(e => e.method === "Безнал").reduce((a, b) => a + b.amount, 0); return { revenueFact, expenseConstant, expenseOneTime, supplierReturn, expenseCash, expenseCard }; }, [currentWeek]); // --- Глобальные итоги (аналог Листа ИТОГ_ВСЕГО) --- const globalStats = useMemo(() => { let totalRevenue = 0; let totalPurchaseFact = 0; // Инвойс - Брак поставщика (если мы так считаем) или просто Инвойс // Логика из промпта: "Закуп фактический" = инвойс (но в промпте сказано что можно вычесть брак) // Давайте считать: Закуп факт = Инвойс. А брак просто статистика. // Или вариант 2: Закуп факт = Инвойс - Брак. Используем Вариант 2 для чистоты. let totalConstant = 0; let totalOneTime = 0; let totalStoreWriteOff = 0; let totalSupplierReturn = 0; let cashOnHand = 0; let bankAccount = 0; weeks.forEach(w => { // Подсчет расходов через reduce внутри недели const expConst = w.expenses.filter(e => e.category === "Постоянный").reduce((s, x) => s + x.amount, 0); const expOneTime = w.expenses.filter(e => e.category === "Разовый").reduce((s, x) => s + x.amount, 0); const expReturn = w.expenses.filter(e => e.category === "Возврат_поставщику").reduce((s, x) => s + x.amount, 0); const expCash = w.expenses.filter(e => e.method === "Наличные").reduce((s, x) => s + x.amount, 0); const expCard = w.expenses.filter(e => e.method === "Безнал").reduce((s, x) => s + x.amount, 0); // Выручка факт (нал + безнал + оплаты долгов) const weekRevenue = w.sales.cash + w.sales.card + w.debtPayments.cash + w.debtPayments.card; // Закуп факт (Инвойс - Брак при поставке) const purchaseFact = w.supply.invoiceTotal - w.supply.writeOffSupplier; totalRevenue += weekRevenue; totalPurchaseFact += purchaseFact; totalConstant += expConst; totalOneTime += expOneTime; totalStoreWriteOff += w.storeWriteOff; totalSupplierReturn += expReturn; // Касса (Нал) // Наличные продажи + оплаты долгов налом – расходы налом – возвраты поставщику налом (если они были налом, упростим что возврат поставщику это приход денег, но в формуле пользователя это "расход"?) // ВНИМАНИЕ: "Возврат поставщику" в контексте "расходы" обычно означает мы вернули товар и нам вернули деньги, ИЛИ мы вернули деньги клиенту? // В промпте: "=СУММЕСЛИ(B2:B100; "Возврат_поставщику"; D2:D100)". Это в блоке Расходы. // Значит, это МЫ платим поставщику за что-то? Или это минус расход? // Обычно "Возврат поставщику" - это товар. Движение денег здесь: Поставщик вернул нам деньги. // Но если это в блоке расходов, предположим, что это отдельная категория, которая НЕ минусуется из прибыли, но влияет на кассу. // Следуя формуле пользователя: "Наличные на руках = ... – возвраты поставщику налом". Значит мы отдали деньги. cashOnHand += (w.sales.cash + w.debtPayments.cash) - expCash; bankAccount += (w.sales.card + w.debtPayments.card) - expCard; }); const netProfit = totalRevenue - totalPurchaseFact - totalConstant - totalStoreWriteOff; return { totalRevenue, totalPurchaseFact, totalConstant, totalOneTime, totalStoreWriteOff, totalSupplierReturn, netProfit, cashOnHand, bankAccount }; }, [weeks]); // --- Должники (Сводная) --- const debtorsSummary = useMemo(() => { const summary = {}; debtors.forEach(d => { if (!summary[d.client]) summary[d.client] = 0; summary[d.client] += d.change; }); return Object.entries(summary).map(([client, total]) => ({ client, total })); }, [debtors]); // --- Форматирование денег --- const fmt = (num) => new Intl.NumberFormat('ru-RU', { style: 'currency', currency: 'RUB', maximumFractionDigits: 0 }).format(num); // --- Рендер --- return (
{/* Навигация */}
{/* Вкладка: Недельный ввод */} {activeTab === 'weekly' && (
{/* Селектор недели */}
Выбор недели:
{/* БЛОК: Поставки и Списания */}
updateWeekVal('supply', 'invoiceTotal', v)} /> updateWeekVal('supply', 'writeOffSupplier', v)} /> updateWeekVal('storeWriteOff', null, v)} /> updateWeekVal('stockBalance', null, v)} />
{/* БЛОК: Продажи */}
updateWeekVal('sales', 'cash', v)} /> updateWeekVal('sales', 'card', v)} /> updateWeekVal('sales', 'credit', v)} />

Погашение долгов (приход)

updateWeekVal('debtPayments', 'cash', v)} /> updateWeekVal('debtPayments', 'card', v)} />
{/* БЛОК: Расходы */}
{currentWeek.expenses.map(exp => ( ))}
Сумма Тип расхода Способ оплаты Комментарий
updateExpense(exp.id, 'amount', parseFloat(e.target.value)||0)} /> updateExpense(exp.id, 'note', e.target.value)} />
{/* БЛОК: Итоги недели (Preview) */}

Факт. Выручка

{fmt(weeklyStats.revenueFact)}

Постоянные Расходы

{fmt(weeklyStats.expenseConstant)}

Разовые Расходы

{fmt(weeklyStats.expenseOneTime)}

Возврат Поставщику

{fmt(weeklyStats.supplierReturn)}

)} {/* Вкладка: ИТОГ ВСЕГО */} {activeTab === 'total' && (

Финансовый результат (Все время)

{/* Ключевые показатели */}

Чистая Прибыль

{fmt(globalStats.netProfit)}

Продажи - Закуп - Постоянные - Списания Магазина

Наличные на руках

{fmt(globalStats.cashOnHand)}

Нал Продажи + Оплата Долгов - Расходы Нал

Деньги на счету (Безнал)

{fmt(globalStats.bankAccount)}

Безнал Продажи + Оплата Долгов - Расходы Безнал

{/* Детальная таблица по месяцам/неделям */}

Детализация по неделям

{weeks.map(w => { const rev = w.sales.cash + w.sales.card + w.debtPayments.cash + w.debtPayments.card; const buy = w.supply.invoiceTotal - w.supply.writeOffSupplier; const constExp = w.expenses.filter(e => e.category === "Постоянный").reduce((s,x)=>s+x.amount,0); return ( ); })}
Неделя Выручка Факт Закуп Факт Пост. Расходы Списания Маг. Остаток склад
{w.name} {fmt(rev)} {fmt(buy)} {fmt(constExp)} {fmt(w.storeWriteOff)} {fmt(w.stockBalance)}
ИТОГО {fmt(globalStats.totalRevenue)} {fmt(globalStats.totalPurchaseFact)} {fmt(globalStats.totalConstant)} {fmt(globalStats.totalStoreWriteOff)} -
)} {/* Вкладка: ДОЛЖНИКИ */} {activeTab === 'debtors' && (
{/* Левая колонка: Сводка */}
    {debtorsSummary.map((d, idx) => (
  • {d.client} 0 ? 'text-red-600' : 'text-green-600'}`}> {fmt(d.total)}
  • ))}
Общий долг: {fmt(debtorsSummary.reduce((acc, curr) => acc + curr.total, 0))}
{/* Правая колонка: Журнал */}
Дата
Клиент
Изменение (+долг/-оплата)
Комментарий
{debtors.map((op) => (
{op.date}
{op.client}
0 ? 'text-red-600' : 'text-green-600'}`}> {op.change > 0 ? '+' : ''}{fmt(op.change)}
{op.comment}
))}
{/* Форма добавления операции */}

Добавить операцию

{ e.preventDefault(); const data = new FormData(e.target); setDebtors([...debtors, { id: Date.now(), date: data.get('date'), client: data.get('client'), change: parseFloat(data.get('change')), comment: data.get('comment') }]); e.target.reset(); }} >
)}
); }
Made on
Tilda