import React, { useEffect, useMemo, useState } from "react"; import { ResponsiveContainer, PieChart, Pie, Cell, Tooltip as RTooltip, BarChart, Bar, XAxis, YAxis, CartesianGrid, LineChart, Line, Legend } from "recharts"; // ===================== // UTILITIES & CONSTANTS // ===================== const CATEGORIES = [ { key: "hayriya", name: "Hayriya", percent: 2.5, source: "Bank" }, { key: "otaOnaga", name: "Ota-onaga", percent: 10, source: "Bank" }, { key: "kelajak", name: "Kelajak uchun", percent: 8.75, source: "Bank" }, { key: "oyin", name: "O'yin-kulgi", percent: 8.75, source: "Bank" }, { key: "kapital", name: "Kapital", percent: 30.8, source: "Bank" }, { key: "kattaOrzu", name: "Katta orzular", percent: 3.85, source: "Bank" }, { key: "kichikOrzu", name: "Kichik orzular", percent: 3.85, source: "Uyda" }, { key: "shaxsiy", name: "Shaxsiy hisob", percent: 31.5, source: "Uyda" }, ]; const DEFAULT_BUSINESSES = [ { id: "nini", name: "NINI bog'cha", active: true }, { id: "restoran", name: "Restoran", active: true }, { id: "online", name: "Onlayn savdo", active: true }, ]; const thousand = (n) => (n ?? 0).toLocaleString("uz-UZ", { maximumFractionDigits: 0 }); const currency = (n) => `${thousand(n)} so'm`; const ls = { get: (k, v) => { try { const raw = localStorage.getItem(k); return raw ? JSON.parse(raw) : v; } catch (e) { return v; } }, set: (k, v) => localStorage.setItem(k, JSON.stringify(v)), }; const nowYYYYMM = () => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}`; }; // ===================== // ROOT COMPONENT // ===================== export default function App() { const [activeTab, setActiveTab] = useState("dashboard"); // Global settings/state saved in LS const [month, setMonth] = useState(ls.get("month", nowYYYYMM())); const [incomePlan, setIncomePlan] = useState(ls.get("incomePlan", 10000000)); const [businesses, setBusinesses] = useState(ls.get("businesses", DEFAULT_BUSINESSES)); const [allowOverspend, setAllowOverspend] = useState(ls.get("allowOverspend", false)); // Data collections by month const [incomes, setIncomes] = useState(ls.get("incomes", {})); // {YYYY-MM: [{date,business,amount, note}]} const [expenses, setExpenses] = useState(ls.get("expenses", {})); // {YYYY-MM: [{date,category,amount,source,note}]} const [transfers, setTransfers] = useState(ls.get("transfers", {})); // {YYYY-MM: [{date, from, to, amount, note}]} const [goals, setGoals] = useState(ls.get("goals", [])); // static list (progress tied to transfers) const [debts, setDebts] = useState(ls.get("debts", { given: [], taken: [] })); // Autosave useEffect(() => ls.set("month", month), [month]); useEffect(() => ls.set("incomePlan", incomePlan), [incomePlan]); useEffect(() => ls.set("businesses", businesses), [businesses]); useEffect(() => ls.set("allowOverspend", allowOverspend), [allowOverspend]); useEffect(() => ls.set("incomes", incomes), [incomes]); useEffect(() => ls.set("expenses", expenses), [expenses]); useEffect(() => ls.set("transfers", transfers), [transfers]); useEffect(() => ls.set("goals", goals), [goals]); useEffect(() => ls.set("debts", debts), [debts]); // Derived: category planned amounts const plans = useMemo(() => { const obj = {}; CATEGORIES.forEach((c) => (obj[c.key] = Math.round(incomePlan * (c.percent / 100)))); return obj; }, [incomePlan]); // Get month arrays helper const getArr = (dict) => dict[month] ?? []; // Sum helpers const sumBy = (arr, key) => arr.reduce((s, x) => s + (x[key] ? Number(x[key]) : 0), 0); // Actual spend per category const spendByCategory = useMemo(() => { const e = getArr(expenses); const res = Object.fromEntries(CATEGORIES.map((c) => [c.key, 0])); e.forEach((x) => { res[x.category] = (res[x.category] || 0) + Number(x.amount); }); return res; }, [expenses, month]); // Transfers impact: from category to goals (only from Kapital by rule, but UI allows generic – we enforce rule on UI) const transfersByCategory = useMemo(() => { const t = getArr(transfers); const out = Object.fromEntries(CATEGORIES.map((c) => [c.key, 0])); const incoming = Object.fromEntries(CATEGORIES.map((c) => [c.key, 0])); t.forEach((x) => { out[x.from] = (out[x.from] || 0) + Number(x.amount); incoming[x.to] = (incoming[x.to] || 0) + Number(x.amount); }); return { out, incoming }; }, [transfers, month]); // Balances (per category) const balances = useMemo(() => { const obj = {}; CATEGORIES.forEach((c) => { const used = (spendByCategory[c.key] || 0) + (transfersByCategory.out[c.key] || 0); obj[c.key] = plans[c.key] - used; }); return obj; }, [plans, spendByCategory, transfersByCategory]); const monthDaysLeft = useMemo(() => { const [y, m] = month.split("-").map(Number); const today = new Date(); const lastDay = new Date(y, m, 0).getDate(); let day = today.getFullYear() === y && today.getMonth() + 1 === m ? today.getDate() : 1; // remaining days including today return lastDay - day + 1; }, [month]); const totalIncomeFact = useMemo(() => sumBy(getArr(incomes), "amount"), [incomes, month]); const totalExpenseFact = useMemo(() => sumBy(getArr(expenses), "amount"), [expenses, month]); // Charts data const pieData = CATEGORIES.map((c) => ({ name: c.name, value: plans[c.key] })); const planVsFactData = CATEGORIES.map((c) => ({ name: c.name, Reja: plans[c.key], Fakt: spendByCategory[c.key] || 0, })); const COLORS = ["#8884d8", "#82ca9d", "#ffc658", "#8dd1e1", "#a4de6c", "#d0ed57", "#ffc0cb", "#d88884"]; // default palette // ============ UI HELPERS ============ const Section = ({ title, children, right }) => (

{title}

{right}
{children}
); const Input = ({ label, ...props }) => ( ); const Select = ({ label, children, ...props }) => ( ); const Button = ({ children, className = "", ...props }) => (
); const KPI = ({ title, value }) => (
{title}
{value}
); function PlanView() { return (
setIncomePlan(Number(e.target.value))} />
{CATEGORIES.map((c) => (
{c.name}
{c.percent}% – Manba: {c.source}
{currency(plans[c.key])}
))}
); } function FaktView() { const [inc, setInc] = useState({ date: todayStr(), business: businesses[0]?.id || "", amount: 0, note: "" }); const [exp, setExp] = useState({ date: todayStr(), category: CATEGORIES[0].key, amount: 0, source: "Bank", note: "" }); return (
setInc({ ...inc, date: e.target.value })} /> setInc({ ...inc, amount: Number(e.target.value) })} /> setInc({ ...inc, note: e.target.value })} />
Joriy oy tushum jami: {currency(totalIncomeFact)}
setExp({ ...exp, date: e.target.value })} /> setExp({ ...exp, amount: Number(e.target.value) })} /> setExp({ ...exp, note: e.target.value })} />
Joriy oy xarajat jami: {currency(totalExpenseFact)}
{getArr(incomes).map((r, i) => ( ))} {getArr(expenses).map((r, i) => ( ))}
Tur Sana Yo'nalish/Toifa Summa Manba Izoh
Tushum {r.date} {businesses.find((b) => b.id === r.business)?.name || r.business} {currency(r.amount)} - {r.note}
Xarajat {r.date} {CATEGORIES.find((c) => c.key === r.category)?.name || r.category} {currency(r.amount)} {r.source} {r.note}
); } function AnalysisView() { return (
currency(v)} />
{CATEGORIES.map((c) => { const plan = plans[c.key]; const fact = spendByCategory[c.key] || 0; const diff = plan - fact; const diffPct = plan ? Math.round((diff / plan) * 100) : 0; return ( ); })}
Toifa Reja Fakt Farq Farq (%)
{c.name} {currency(plan)} {currency(fact)} {currency(diff)} {diffPct}%
); } function BusinessView() { const [name, setName] = useState(""); return (
setName(e.target.value)} placeholder="Yo'nalish nomi" className="flex-1 border rounded-xl px-3 py-2" />
{businesses.map((b, i) => (
{b.name}
ID: {b.id}
))}
); } function DebtsView() { const [tab, setTab] = useState("given"); const [row, setRow] = useState({ who: "", start: todayStr(), principal: 0, rate: 0, termMonths: 12, schedule: "oylik", nextDue: todayStr(), note: "" }); const add = () => { const copy = { ...debts }; copy[tab] = [...copy[tab], row]; setDebts(copy); setRow({ who: "", start: todayStr(), principal: 0, rate: 0, termMonths: 12, schedule: "oylik", nextDue: todayStr(), note: "" }); }; const series = (list) => list.map((d, i) => ({ name: d.who || `${tab} ${i+1}`, Qoldiq: Math.max(0, d.principal) })); return (
}>
setRow({...row, who:e.target.value})} /> setRow({...row, start:e.target.value})} /> setRow({...row, principal:Number(e.target.value)})} /> setRow({...row, rate:Number(e.target.value)})} /> setRow({...row, termMonths:Number(e.target.value)})} /> setRow({...row, nextDue:e.target.value})} /> setRow({...row, note:e.target.value})} />
currency(v)} />
{debts[tab].map((d, i) => ( ))}
Kim Asosiy Foiz % Muddat (oy) Keyingi to'lov Izoh
{d.who} {currency(d.principal)} {d.rate} {d.termMonths} {d.nextDue} {d.note}
); } function GoalsView() { const [g, setG] = useState({ name: "", target: 0, months: 12, priority: "O'rta", status: "Aktiv" }); const [t, setT] = useState({ date: todayStr(), amount: 0, toGoal: "" }); const addGoal = () => { if (!g.name.trim()) return; const id = cryptoId(); setGoals([...goals, { id, ...g }]); setG({ name: "", target: 0, months: 12, priority: "O'rta", status: "Aktiv" }); }; const doTransfer = () => { if (!t.toGoal) return alert("Maqsad tanlanmagan"); addTransfer({ date: t.date, from: "kapital", to: `goal:${t.toGoal}`, amount: Number(t.amount), note: "Kapitaldan ko'chirish" }); setT({ ...t, amount: 0 }); }; return (
setG({...g, name:e.target.value})} /> setG({...g, target:Number(e.target.value)})} /> setG({...g, months:Number(e.target.value)})} />
setT({...t, date:e.target.value})} /> setT({...t, amount:Number(e.target.value)})} />
{goals.map((gg) => { const prog = goalProgress(gg.id); const pct = Math.min(100, Math.round((prog / Math.max(1, gg.target)) * 100)); return (
{gg.name}
{currency(prog)} / {currency(gg.target)} ({pct}%)
); })}
); } function DailyView() { // quick add mirrors Fakt expense form but with extra helper outputs const [exp, setExp] = useState({ date: todayStr(), category: CATEGORIES[0].key, amount: 0, source: "Bank", note: "" }); const remainingDays = monthDaysLeft; const perDay = (cat) => Math.max(0, Math.floor((balances[cat] || 0) / Math.max(1, remainingDays))); return (
setExp({...exp, date:e.target.value})} /> setExp({...exp, amount:Number(e.target.value)})} /> setExp({...exp, note:e.target.value})} />
{CATEGORIES.map((c)=> (
{c.name}
Qoldiq: {currency(balances[c.key])}
Kunlik: {currency(perDay(c.key))}
))}
); } function SettingsView() { return (
); } // ============ RENDER ============ return (
Moliyaviy boshqaruv – Plan/Fakt
{[ { id: "dashboard", label: "Bosh sahifa" }, { id: "plan", label: "Plan" }, { id: "fakt", label: "Fakt" }, { id: "analysis", label: "Plan vs Fakt" }, { id: "business", label: "Biznes yo'nalishlari" }, { id: "debts", label: "Qarzlar" }, { id: "goals", label: "Maqsadlar & Investitsiya" }, { id: "daily", label: "Kunlik xarajatlar" }, { id: "settings", label: "Sozlamalar" }, ].map((t) => ( ))}
{activeTab === "dashboard" && } {activeTab === "plan" && } {activeTab === "fakt" && } {activeTab === "analysis" && } {activeTab === "business" && } {activeTab === "debts" && } {activeTab === "goals" && } {activeTab === "daily" && } {activeTab === "settings" && }
© {new Date().getFullYear()} – Moliyaviy boshqaruv (UZ). Ma'lumotlar brauzeringizda saqlanadi.
); } // ===================== // HELPERS // ===================== function slug(s) { return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, ""); } function todayStr(){ const d=new Date(); return d.toISOString().slice(0,10); } function cryptoId(){ return Math.random().toString(36).slice(2,9); } function renderMonthOptions() { const arr = []; const base = new Date(); base.setMonth(base.getMonth() - 11); for (let i = 0; i < 24; i++) { const y = base.getFullYear(); const m = String(base.getMonth() + 1).padStart(2, "0"); arr.push(`${y}-${m}`); base.setMonth(base.getMonth() + 1); } return arr.map((mm) => ); } function monthlyExpenseSeries(expenses){ // sum by month for all records const out = {}; Object.keys(expenses).forEach((mm)=>{ out[mm] = (expenses[mm]||[]).reduce((s,x)=> s + Number(x.amount||0), 0); }); return Object.keys(out).sort().map((mm)=> ({ month:mm, Xarajat: out[mm] })); }