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)} />
{/* БЛОК: Расходы */}
{/* БЛОК: Итоги недели (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}
))}
{/* Форма добавления операции */}
Добавить операцию
)}
);
}