161 lines
7.5 KiB
JavaScript
161 lines
7.5 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import StatCard from './Components/StatCard';
|
|
import AccountsTable from './Components/AccountsTable';
|
|
import { DollarSign, TrendingDown, TrendingUp, Calendar, ChevronDown } from 'lucide-react';
|
|
|
|
export default function Dashboard() {
|
|
const [stats, setStats] = useState({
|
|
total_income: 0,
|
|
total_expense: 0,
|
|
net_profit: 0,
|
|
low_stock_count: 0,
|
|
transactions: []
|
|
});
|
|
const [branches, setBranches] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [filterBranch, setFilterBranch] = useState('');
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const [paginationData, setPaginationData] = useState(null);
|
|
const [startDate, setStartDate] = useState(new Date().toISOString().split('T')[0]);
|
|
const [endDate, setEndDate] = useState(new Date().toISOString().split('T')[0]);
|
|
|
|
useEffect(() => {
|
|
fetch('/api/branches?status=Active')
|
|
.then(res => res.json())
|
|
.then(data => setBranches(data))
|
|
.catch(err => console.error("Error fetching branches:", err));
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
setLoading(true);
|
|
const params = new URLSearchParams({
|
|
branch_id: filterBranch,
|
|
start_date: startDate,
|
|
end_date: endDate,
|
|
page: currentPage
|
|
});
|
|
fetch(`/api/reports/profit?${params}`)
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
setStats({
|
|
total_income: data.total_income,
|
|
total_expense: data.total_expense,
|
|
net_profit: data.net_profit,
|
|
low_stock_count: data.low_stock_count,
|
|
transactions: data.transactions || []
|
|
});
|
|
setPaginationData(data.pagination);
|
|
setLoading(false);
|
|
})
|
|
.catch(err => {
|
|
console.error("Error fetching dashboard stats:", err);
|
|
setLoading(false);
|
|
});
|
|
}, [filterBranch, startDate, endDate, currentPage]);
|
|
|
|
// Reset page when filters change
|
|
useEffect(() => {
|
|
setCurrentPage(1);
|
|
}, [filterBranch, startDate, endDate]);
|
|
|
|
const formatCurrency = (val) => {
|
|
if (val === undefined || val === null || isNaN(val)) return 'AED 0.00';
|
|
return new Intl.NumberFormat('en-AE', { style: 'currency', currency: 'AED' }).format(val);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<main className="p-8 max-w-[1600px] mx-auto space-y-8">
|
|
{/* Dashboard Title & Filters */}
|
|
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
|
<h2 className="text-4xl font-extrabold text-gray-900 tracking-tight">Owner Dashboard</h2>
|
|
|
|
<div className="flex items-center gap-3">
|
|
{/* Branch Selector */}
|
|
<div className="relative group">
|
|
<select
|
|
value={filterBranch}
|
|
onChange={(e) => setFilterBranch(e.target.value)}
|
|
className="appearance-none pl-4 pr-10 py-2.5 bg-white border border-gray-200 rounded-xl text-sm font-bold text-gray-600 focus:border-red-500/30 focus:ring-4 focus:ring-red-500/5 transition-all outline-none max-w-[200px] truncate"
|
|
>
|
|
<option value="">All Branches</option>
|
|
{branches.map(b => (
|
|
<option key={b.id} value={b.id}>
|
|
{b.name.length > 30 ? b.name.substring(0, 30) + '...' : b.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<ChevronDown size={14} className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 group-focus-within:text-red-500 transition-all" />
|
|
</div>
|
|
|
|
{/* Date Range */}
|
|
<div className="flex items-center bg-white border border-gray-200 rounded-xl p-1 gap-1">
|
|
<div className="flex items-center gap-2 px-3 py-1.5 text-xs font-bold text-gray-500 border-r border-gray-100">
|
|
<span className="text-gray-400 font-medium whitespace-nowrap">From</span>
|
|
<input
|
|
type="date"
|
|
value={startDate}
|
|
onChange={(e) => setStartDate(e.target.value)}
|
|
className="bg-transparent border-none p-0 focus:ring-0 text-gray-700 outline-none w-28"
|
|
/>
|
|
<Calendar size={14} className="text-gray-400" />
|
|
</div>
|
|
<div className="flex items-center gap-2 px-3 py-1.5 text-xs font-bold text-gray-500">
|
|
<span className="text-gray-400 font-medium whitespace-nowrap">To</span>
|
|
<input
|
|
type="date"
|
|
value={endDate}
|
|
onChange={(e) => setEndDate(e.target.value)}
|
|
className="bg-transparent border-none p-0 focus:ring-0 text-gray-700 outline-none w-28"
|
|
/>
|
|
<Calendar size={14} className="text-gray-400" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stat Cards Grid */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
|
<StatCard
|
|
title="Total Income Collected"
|
|
subtitle="Total Credits"
|
|
value={loading ? "..." : formatCurrency(stats.total_income)}
|
|
color="green"
|
|
icon={DollarSign}
|
|
/>
|
|
<StatCard
|
|
title="Total Expenses Paid"
|
|
subtitle="Total Debits"
|
|
value={loading ? "..." : formatCurrency(stats.total_expense)}
|
|
color="red"
|
|
icon={TrendingDown}
|
|
/>
|
|
<StatCard
|
|
title="Total Net Savings"
|
|
subtitle="Net Profit"
|
|
value={loading ? "..." : formatCurrency(stats.net_profit)}
|
|
color="blue"
|
|
icon={TrendingUp}
|
|
/>
|
|
<StatCard
|
|
title="Items Below Reorder Level"
|
|
subtitle="Low Stock Products"
|
|
value={loading ? "..." : stats.low_stock_count}
|
|
color="red"
|
|
icon={TrendingDown}
|
|
/>
|
|
</div>
|
|
|
|
{/* Main Content Area */}
|
|
<div className="grid grid-cols-1 gap-8">
|
|
<AccountsTable
|
|
data={stats.transactions}
|
|
pagination={paginationData}
|
|
onPageChange={(p) => setCurrentPage(p)}
|
|
/>
|
|
</div>
|
|
</main>
|
|
</>
|
|
);
|
|
}
|