Counter dashboard global search added

This commit is contained in:
Sidharth Prabhu
2026-06-24 08:08:12 +05:30
parent a914fd44cc
commit c3bb004dc6
10 changed files with 252 additions and 114 deletions

View File

@@ -80,9 +80,7 @@ function App() {
<Route path="purchases/intent/receives-dashboard" element={<IntentDashboard title="RECEIVABLE DASHBOARD" />} />
<Route path="purchases/intent/orders" element={<IntentList title="ORDERS" />} />
<Route path="purchases/intent/receives" element={<IntentList title="RECEIVES" />} />
<Route path="purchases/intent/receives-summary" element={<PlaceholderPage title="Receives Summary" />} />
<Route path="purchases/intent/request" element={<PlaceholderPage title="Intent Request" />} />
<Route path="purchases/intent/stores" element={<PlaceholderPage title="Intent Stores" />} />
{/* Inventory */}
<Route path="inventory/new-arrivals" element={<NewArrivals />} />

View File

@@ -80,10 +80,7 @@ const menuItems: MenuItem[] = [
{ title: 'Orders Dashboard', path: '/purchases/intent/orders-dashboard' },
{ title: 'Receives Dashboard', path: '/purchases/intent/receives-dashboard' },
{ title: 'Orders', path: '/purchases/intent/orders' },
{ title: 'Receives', path: '/purchases/intent/receives' },
{ title: 'Receives Summary', path: '/purchases/intent/receives-summary' },
{ title: 'Request', path: '/purchases/intent/request' },
{ title: 'Stores', path: '/purchases/intent/stores' }
{ title: 'Receives', path: '/purchases/intent/receives' }
]
}
]

View File

@@ -19,18 +19,86 @@ import { motion } from 'framer-motion';
const PurchaseSummary = () => {
const [activeRange, setActiveRange] = useState('Today');
const [customDates, setCustomDates] = useState({ from: '', to: '' });
const [data, setData] = useState<any>(null);
const [bills, setBills] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
const toLocalISOString = (date: Date) => {
const tzo = -date.getTimezoneOffset(),
pad = (num: number) => {
const norm = Math.floor(Math.abs(num));
return (norm < 10 ? '0' : '') + norm;
};
return date.getFullYear() +
'-' + pad(date.getMonth() + 1) +
'-' + pad(date.getDate()) +
'T' + pad(date.getHours()) +
':' + pad(date.getMinutes()) +
':' + pad(date.getSeconds()) +
'.' + pad(date.getMilliseconds());
};
const getRangeDates = (range: string) => {
const now = new Date();
const start = new Date();
const end = new Date();
// Set end to end of today
end.setHours(23, 59, 59, 999);
switch (range) {
case 'Today':
start.setHours(0, 0, 0, 0);
break;
case 'Yesterday':
start.setDate(now.getDate() - 1);
start.setHours(0, 0, 0, 0);
end.setDate(now.getDate() - 1);
end.setHours(23, 59, 59, 999);
break;
case 'Week':
start.setDate(now.getDate() - 7);
start.setHours(0, 0, 0, 0);
break;
case '30 Days':
start.setDate(now.getDate() - 30);
start.setHours(0, 0, 0, 0);
break;
case 'Custom':
if (customDates.from && customDates.to) {
const from = new Date(customDates.from);
from.setHours(0, 0, 0, 0);
const to = new Date(customDates.to);
to.setHours(23, 59, 59, 999);
return { from: toLocalISOString(from), to: toLocalISOString(to) };
}
return null;
default:
start.setHours(0, 0, 0, 0);
}
return { from: toLocalISOString(start), to: toLocalISOString(end) };
};
useEffect(() => {
const fetchData = async () => {
try {
setIsLoading(true);
// Fetch summary and bills in parallel
const range = getRangeDates(activeRange);
if (!range) {
setIsLoading(false);
return;
}
const params = new URLSearchParams();
params.append('from', range.from);
params.append('to', range.to);
const queryStr = `?${params.toString()}`;
// Fetch summary and bills in parallel with range params
const [summaryRes, billsRes] = await Promise.all([
apiFetch('/api/purchases/summary'),
apiFetch('/api/purchases/orders')
apiFetch(`/api/purchases/summary${queryStr}`),
apiFetch(`/api/purchases/orders${queryStr}`)
]);
if (summaryRes.ok && billsRes.ok) {
@@ -46,7 +114,7 @@ const PurchaseSummary = () => {
}
};
fetchData();
}, []);
}, [activeRange, customDates]);
if (isLoading || !data) {
return (
@@ -84,18 +152,37 @@ const PurchaseSummary = () => {
<h1 className="text-2xl font-black text-slate-800">Summary</h1>
</div>
<div className="flex items-center gap-2 bg-white p-1 rounded-2xl border border-slate-200 shadow-sm">
{['Yesterday', 'Today', 'Week', '30 Days'].map(range => (
<button
key={range}
onClick={() => setActiveRange(range)}
className={`px-4 py-2 text-[10px] font-black uppercase tracking-widest transition-all rounded-xl ${activeRange === range ? 'text-white bg-[#003317] shadow-md shadow-[#003317]/20' : 'text-slate-400 hover:text-slate-600'}`}
>
{range}
</button>
))}
</div>
</div>
<div className="flex flex-col items-end gap-2">
<div className="flex items-center gap-2 bg-white p-1 rounded-2xl border border-slate-200 shadow-sm">
{['Yesterday', 'Today', 'Week', '30 Days', 'Custom'].map(range => (
<button
key={range}
onClick={() => setActiveRange(range)}
className={`px-4 py-2 text-[10px] font-black uppercase tracking-widest transition-all rounded-xl ${activeRange === range ? 'text-white bg-[#003317] shadow-md shadow-[#003317]/20' : 'text-slate-400 hover:text-slate-600'}`}
>
{range}
</button>
))}
</div>
{activeRange === 'Custom' && (
<div className="flex items-center gap-2 animate-in fade-in slide-in-from-top-1 duration-300">
<input
type="date"
value={customDates.from}
onChange={(e) => setCustomDates({ ...customDates, from: e.target.value })}
className="bg-white border border-slate-200 rounded-lg px-2 py-1.5 text-[10px] font-bold text-slate-600 outline-none focus:border-[#003317]"
/>
<span className="text-slate-300 text-[10px] font-bold">to</span>
<input
type="date"
value={customDates.to}
onChange={(e) => setCustomDates({ ...customDates, to: e.target.value })}
className="bg-white border border-slate-200 rounded-lg px-2 py-1.5 text-[10px] font-bold text-slate-600 outline-none focus:border-[#003317]"
/>
</div>
)}
</div>
</div>
<div className="grid grid-cols-12 gap-8">
<div className="col-span-12 lg:col-span-5 space-y-6">

View File

@@ -1,4 +1,4 @@
import { apiFetch } from '../api';
import { apiFetch } from '../api';
import { useState, useEffect } from 'react';
import {
ChevronRight,
@@ -27,6 +27,7 @@ import { motion } from 'framer-motion';
const StoreDashboard = () => {
const [activeTab, setActiveTab] = useState('Sales');
const [timeRange, setTimeRange] = useState('Today');
const [customDates, setCustomDates] = useState({ from: '', to: '' });
const [stats, setStats] = useState({
totalSales: 0,
activeOrders: 0,
@@ -85,6 +86,15 @@ const StoreDashboard = () => {
start.setDate(now.getDate() - 30);
start.setHours(0, 0, 0, 0);
break;
case 'Custom':
if (customDates.from && customDates.to) {
const from = new Date(customDates.from);
from.setHours(0, 0, 0, 0);
const to = new Date(customDates.to);
to.setHours(23, 59, 59, 999);
return { from: toLocalISOString(from), to: toLocalISOString(to) };
}
return null;
default:
start.setHours(0, 0, 0, 0);
}
@@ -95,12 +105,16 @@ const StoreDashboard = () => {
const fetchStats = async () => {
try {
setIsLoading(true);
const range = getRangeDates(timeRange);
const params = new URLSearchParams();
params.append('from', range.from);
params.append('to', range.to);
const range = getRangeDates(timeRange);
if (!range) {
setIsLoading(false);
return;
}
const params = new URLSearchParams();
params.append('from', range.from);
params.append('to', range.to);
const response = await apiFetch(`/api/dashboard/stats?${params.toString()}`);
const response = await apiFetch(`/api/dashboard/stats?${params.toString()}`);
if (response.ok) {
const data = await response.json();
console.log('Dashboard data received successfully:', data);
@@ -138,7 +152,7 @@ const StoreDashboard = () => {
}
};
fetchStats();
}, [timeRange]);
}, [timeRange, customDates]);
const pieData = [
{ name: 'Full Payment', value: Number(stats.totalSales) || 0, color: '#8b5cf6' },
@@ -263,22 +277,41 @@ const StoreDashboard = () => {
{/* Right Stats Column */}
<div className="col-span-12 lg:col-span-3 space-y-6">
{/* Filters */}
<div className="flex items-center justify-between overflow-x-auto gap-2 pb-2 scrollbar-none">
{['Yesterday', 'Today', 'Week', '30 Days', 'Custom'].map(range => (
<button
key={range}
title={`Filter metrics by ${range.toLowerCase()}`}
onClick={() => setTimeRange(range)}
className={`whitespace-nowrap px-2 py-2 text-[10px] font-black uppercase tracking-tighter transition-all relative ${timeRange === range ? 'text-[#003317]' : 'text-slate-400 hover:text-slate-600'}`}
>
{range}
{timeRange === range && (
<motion.div layoutId="rangeLineStore" className="absolute bottom-0 left-0 right-0 h-0.5 bg-[#003317]" />
)}
</button>
))}
</div>
{/* Filters */}
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between overflow-x-auto gap-2 pb-2 scrollbar-none">
{['Yesterday', 'Today', 'Week', '30 Days', 'Custom'].map(range => (
<button
key={range}
title={`Filter metrics by ${range.toLowerCase()}`}
onClick={() => setTimeRange(range)}
className={`whitespace-nowrap px-2 py-2 text-[10px] font-black uppercase tracking-tighter transition-all relative ${timeRange === range ? 'text-[#003317]' : 'text-slate-400 hover:text-slate-600'}`}
>
{range}
{timeRange === range && (
<motion.div layoutId="rangeLineStore" className="absolute bottom-0 left-0 right-0 h-0.5 bg-[#003317]" />
)}
</button>
))}
</div>
{timeRange === 'Custom' && (
<div className="flex items-center gap-2 animate-in fade-in slide-in-from-top-1 duration-300">
<input
type="date"
value={customDates.from}
onChange={(e) => setCustomDates({ ...customDates, from: e.target.value })}
className="flex-1 bg-white border border-slate-200 rounded-lg px-2 py-1.5 text-[10px] font-bold text-slate-600 outline-none focus:border-[#003317]"
/>
<span className="text-slate-300 text-[10px] font-bold">to</span>
<input
type="date"
value={customDates.to}
onChange={(e) => setCustomDates({ ...customDates, to: e.target.value })}
className="flex-1 bg-white border border-slate-200 rounded-lg px-2 py-1.5 text-[10px] font-bold text-slate-600 outline-none focus:border-[#003317]"
/>
</div>
)}
</div>
{/* Total Orders Card */}
<div title="View detailed store volume and throughput" className="bg-white p-6 rounded-[24px] border border-slate-100 shadow-sm relative h-[180px] flex flex-col justify-between group cursor-pointer hover:border-[#003317]/30 transition-all">