import React, { useState, useEffect, useMemo } from 'react'; import { Plus, Minus, DollarSign, TrendingUp, TrendingDown, Calendar, PieChart, Wallet, CreditCard, ArrowRight, ChevronLeft, ChevronRight, Briefcase, Building, RefreshCw, Clock, Layers } from 'lucide-react'; // Firebase Imports import { initializeApp } from "firebase/app"; import { getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken } from "firebase/auth"; import { getFirestore, collection, addDoc, onSnapshot, query, orderBy, deleteDoc, doc, Timestamp, where } from "firebase/firestore"; // --- Configuration & Helpers --- 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 : 'powerad-fin-v1'; // Theme Colors - PoweRad Branding const THEME = { primary: "bg-red-700 hover:bg-red-800 text-white", primaryLight: "bg-red-50 text-red-700", secondary: "bg-slate-900 text-white", accent: "text-red-600", success: "text-emerald-600 bg-emerald-50", danger: "text-rose-600 bg-rose-50", neutral: "bg-gray-100 text-gray-800", card: "bg-white shadow-sm border border-slate-100", }; // Date Helpers const getStartOfPeriod = (date, period) => { const d = new Date(date); d.setHours(0, 0, 0, 0); if (period === 'weekly') { const day = d.getDay(); const diff = d.getDate() - day + (day === 0 ? -6 : 1); // Adjust when day is sunday d.setDate(diff); } else if (period === 'monthly') { d.setDate(1); } else if (period === 'yearly') { d.setMonth(0, 1); } return d; }; const getEndOfPeriod = (date, period) => { const d = new Date(date); d.setHours(23, 59, 59, 999); if (period === 'weekly') { const day = d.getDay(); const diff = d.getDate() - day + (day === 0 ? 0 : 7); d.setDate(diff); } else if (period === 'monthly') { d.setMonth(d.getMonth() + 1, 0); } else if (period === 'yearly') { d.setMonth(11, 31); } return d; }; const formatDate = (date) => { return new Date(date).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric' }); }; const formatCurrency = (amount) => { return new Intl.NumberFormat('en-GB', { style: 'currency', currency: 'GBP', minimumFractionDigits: 0, maximumFractionDigits: 0, }).format(amount); }; // --- Main Component --- export default function PoweRadFinance() { const [user, setUser] = useState(null); const [transactions, setTransactions] = useState([]); const [assets, setAssets] = useState([]); const [liabilities, setLiabilities] = useState([]); const [loading, setLoading] = useState(true); // View State const [period, setPeriod] = useState('monthly'); // daily, weekly, monthly, yearly const [currentDate, setCurrentDate] = useState(new Date()); const [activeTab, setActiveTab] = useState('dashboard'); // dashboard, transactions, balance // Input Modal State const [isModalOpen, setIsModalOpen] = useState(false); const [modalType, setModalType] = useState('income'); // income, expense, asset, liability // --- Auth & Data Fetching --- useEffect(() => { const initAuth = async () => { if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) { await signInWithCustomToken(auth, __initial_auth_token); } else { await signInAnonymously(auth); } }; initAuth(); return onAuthStateChanged(auth, setUser); }, []); useEffect(() => { if (!user) return; const txRef = collection(db, 'artifacts', appId, 'users', user.uid, 'transactions'); const txQuery = query(txRef); const unsubTx = onSnapshot(txQuery, (snapshot) => { const data = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data(), date: doc.data().date?.toDate() || new Date() })); setTransactions(data.sort((a, b) => b.date - a.date)); setLoading(false); }, (error) => console.error("Tx Error", error)); const bsRef = collection(db, 'artifacts', appId, 'users', user.uid, 'balance_sheet'); const bsQuery = query(bsRef); const unsubBs = onSnapshot(bsQuery, (snapshot) => { const data = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); setAssets(data.filter(i => i.type === 'asset')); setLiabilities(data.filter(i => i.type === 'liability')); }, (error) => console.error("BS Error", error)); return () => { unsubTx(); unsubBs(); }; }, [user]); // --- Derived Data --- const periodData = useMemo(() => { const start = getStartOfPeriod(currentDate, period); const end = getEndOfPeriod(currentDate, period); return transactions.filter(t => t.date >= start && t.date <= end); }, [transactions, period, currentDate]); const totals = useMemo(() => { const income = periodData.filter(t => t.type === 'income').reduce((acc, curr) => acc + Number(curr.amount), 0); const expenses = periodData.filter(t => t.type === 'expense').reduce((acc, curr) => acc + Number(curr.amount), 0); const totalAssets = assets.reduce((acc, curr) => acc + Number(curr.amount), 0); const totalLiabilities = liabilities.reduce((acc, curr) => acc + Number(curr.amount), 0); return { income, expenses, netProfit: income - expenses, assets: totalAssets, liabilities: totalLiabilities, netWorth: totalAssets - totalLiabilities }; }, [periodData, assets, liabilities]); const breakdown = useMemo(() => { const categories = {}; periodData.filter(t => t.type === 'expense').forEach(t => { categories[t.category] = (categories[t.category] || 0) + Number(t.amount); }); return Object.entries(categories) .sort(([,a], [,b]) => b - a) .slice(0, 5); // Top 5 }, [periodData]); // Group Assets & Liabilities const groupedBalanceSheet = useMemo(() => { return { currentAssets: assets.filter(a => a.classification === 'Current Asset' || !a.classification), fixedAssets: assets.filter(a => a.classification === 'Fixed Asset'), currentLiabilities: liabilities.filter(l => l.classification === 'Current Liability' || !l.classification), longTermLiabilities: liabilities.filter(l => l.classification === 'Long-Term Liability'), }; }, [assets, liabilities]); // --- Actions --- const handleAdd = async (formData) => { if (!user) return; const txCollection = collection(db, 'artifacts', appId, 'users', user.uid, 'transactions'); const bsCollection = collection(db, 'artifacts', appId, 'users', user.uid, 'balance_sheet'); try { if (['income', 'expense'].includes(formData.type)) { await addDoc(txCollection, { type: formData.type, amount: Number(formData.amount), category: formData.category, frequency: formData.frequency, // New field description: formData.description, date: Timestamp.fromDate(new Date(formData.date)), createdAt: Timestamp.now() }); } else { await addDoc(bsCollection, { type: formData.type, name: formData.category, classification: formData.classification, // New field amount: Number(formData.amount), description: formData.description, }); } setIsModalOpen(false); } catch (e) { console.error("Error adding document: ", e); } }; const handleDelete = async (collectionName, id) => { if (!user) return; try { await deleteDoc(doc(db, 'artifacts', appId, 'users', user.uid, collectionName, id)); } catch (e) { console.error("Delete error", e); } }; const shiftDate = (direction) => { const newDate = new Date(currentDate); if (period === 'daily') newDate.setDate(newDate.getDate() + direction); if (period === 'weekly') newDate.setDate(newDate.getDate() + (direction * 7)); if (period === 'monthly') newDate.setMonth(newDate.getMonth() + direction); if (period === 'yearly') newDate.setFullYear(newDate.getFullYear() + direction); setCurrentDate(newDate); }; // --- UI Components --- const Modal = () => (

Add {modalType}

{ e.preventDefault(); const formData = new FormData(e.target); handleAdd({ type: modalType, amount: formData.get('amount'), category: formData.get('category'), classification: formData.get('classification'), frequency: formData.get('frequency'), description: formData.get('description'), date: formData.get('date') || new Date().toISOString(), }); }} className="p-6 space-y-4" > {/* Amount Field */}
£
{/* Main Category / Name */}
{/* CLASSIFICATION & FREQUENCY SELECTORS */} {/* Income/Expense Frequency */} {['income', 'expense'].includes(modalType) && (
)} {/* Asset Classification */} {modalType === 'asset' && (
)} {/* Liability Classification */} {modalType === 'liability' && (
)} {/* Date Picker (Income/Expense Only) */} {['income', 'expense'].includes(modalType) && (
)}
); // --- Sub-components for Balance Sheet --- const BalanceGroup = ({ title, items, total, type, onDelete }) => (

{title}

{formatCurrency(total)}
{items.length === 0 ? (
No items in this category
) : (
    {items.map(item => (
  • {item.name}

    {item.description || item.classification}

    {formatCurrency(item.amount)}
  • ))}
)}
); if (loading) return
Loading PoweRad Finance...
; return (
{isModalOpen && } {/* Header */}
PR

PoweRad Finance

Business Intelligence

User ID: {user?.uid?.slice(0,6)}...
{/* Navigation Tabs */}
{['dashboard', 'transactions', 'balance'].map((tab) => ( ))}
{/* --- DASHBOARD VIEW --- */} {activeTab === 'dashboard' && (
{/* Period Selector */}
{['daily', 'weekly', 'monthly', 'yearly'].map(p => ( ))}
{period === 'daily' ? formatDate(currentDate) : period === 'yearly' ? currentDate.getFullYear() : period === 'monthly' ? currentDate.toLocaleDateString('en-GB', { month: 'long', year: 'numeric' }) : `Week of ${formatDate(getStartOfPeriod(currentDate, 'weekly'))}` }
{/* Main Stats Grid */}
{/* Income */}
Income

{formatCurrency(totals.income)}

{/* Expenses */}
Expenses

{formatCurrency(totals.expenses)}

{/* Net Profit */}
= 0 ? 'border-l-slate-800' : 'border-l-orange-500'}`}>
Net Profit

= 0 ? 'text-slate-800' : 'text-orange-600'}`}> {formatCurrency(totals.netProfit)}

{totals.income > 0 ? ((totals.netProfit / totals.income) * 100).toFixed(1) : 0}% Margin
{/* Quick Chart / Breakdown */}

Top Expenses

{breakdown.length === 0 ? (
No expenses for this period
) : (
{breakdown.map(([cat, amount], i) => (
{cat} {formatCurrency(amount)}
))}
)}

Financial Health

Total Assets {formatCurrency(totals.assets)}
Total Liabilities {formatCurrency(totals.liabilities)}
Net Worth = 0 ? 'text-slate-900' : 'text-red-600'}`}> {formatCurrency(totals.netWorth)}
)} {/* --- TRANSACTIONS VIEW --- */} {activeTab === 'transactions' && (

Transaction History

{periodData.length === 0 ? ( ) : ( periodData.map((t) => ( )) )}
Date Type Category Description Amount Action
No transactions found for this period.
{formatDate(t.date)} {t.frequency && t.frequency !== 'One-Time' ? ( {t.frequency} ) : ( One-Time )} {t.category} {t.description || '-'} {t.type === 'income' ? '+' : '-'}{formatCurrency(t.amount)}
)} {/* --- BALANCE SHEET VIEW --- */} {activeTab === 'balance' && (
{/* Assets Column */}

Assets

s + i.amount, 0)} type="asset" onDelete={handleDelete} /> s + i.amount, 0)} type="asset" onDelete={handleDelete} />
Total Assets {formatCurrency(totals.assets)}
{/* Liabilities Column */}

Liabilities

s + i.amount, 0)} type="liability" onDelete={handleDelete} /> s + i.amount, 0)} type="liability" onDelete={handleDelete} />
Total Liabilities {formatCurrency(totals.liabilities)}
)}
{/* Floating Action Button (Mobile) */}
); }