diff --git a/client/src/pages/Analytics.tsx b/client/src/pages/Analytics.tsx index bbf2843..b4099ef 100644 --- a/client/src/pages/Analytics.tsx +++ b/client/src/pages/Analytics.tsx @@ -11,7 +11,92 @@ const Analytics = () => { const { customers } = useSelector((state: RootState) => state.customers); const { expenses } = useSelector((state: RootState) => state.expenses); - const [dateRange, setDateRange] = useState('2026'); + const [dateRange, setDateRange] = useState(() => { + // Default to current year + return new Date().getFullYear().toString(); + }); + const [customStartDate, setCustomStartDate] = useState(''); + const [customEndDate, setCustomEndDate] = useState(''); + + // Generate dynamic date range options based on actual order data + const dateRangeOptions = useMemo(() => { + const presets = [ + { value: 'all', label: 'All Time', type: 'preset' }, + { value: 'week', label: 'Last 7 Days', type: 'preset' }, + { value: 'month', label: 'Last 30 Days', type: 'preset' }, + { value: 'quarter', label: 'Last 90 Days', type: 'preset' }, + { value: 'year', label: 'This Year (Calendar)', type: 'preset' }, + { value: 'custom', label: 'Custom Date Range', type: 'preset' } + ]; + + if (!orders || orders.length === 0) { + return { + presets, + years: [], + quarters: [], + months: [] + }; + } + + // Get date ranges from actual order data + const years = new Set(); + const months = new Set(); + const quarters = new Set(); + + orders.forEach(order => { + if (order.dateOrdered) { + const date = new Date(order.dateOrdered); + const year = date.getFullYear(); + const month = date.getMonth(); + const quarter = Math.floor(month / 3) + 1; + + years.add(year); + months.add(`${year}-${String(month + 1).padStart(2, '0')}`); + quarters.add(`${year}-Q${quarter}`); + } + }); + + // Add year options (sorted newest first) + const yearOptions = Array.from(years) + .sort((a, b) => b - a) + .map(year => ({ + value: year.toString(), + label: year.toString(), + type: 'year' + })); + + // Add quarter options (last 8 quarters) + const quarterOptions = Array.from(quarters) + .sort((a, b) => b.localeCompare(a)) + .slice(0, 8) + .map(quarter => ({ + value: quarter, + label: quarter.replace('-', ' '), + type: 'quarter' + })); + + // Add month options (last 24 months) + const monthOptions = Array.from(months) + .sort((a, b) => b.localeCompare(a)) + .slice(0, 24) + .map(monthKey => { + const [year, month] = monthKey.split('-'); + const date = new Date(parseInt(year), parseInt(month) - 1); + const label = date.toLocaleDateString('en-AU', { year: 'numeric', month: 'long' }); + return { + value: monthKey, + label, + type: 'month' + }; + }); + + return { + presets, + years: yearOptions, + quarters: quarterOptions, + months: monthOptions + }; + }, [orders]); // Helper function to get updated printing cost using current product costs const getUpdatedPrintingCost = (order: any) => { @@ -45,6 +130,15 @@ const Analytics = () => { return orderYear === targetYear && orderQuarter === targetQuarter; } + // Handle custom date range format (e.g., "2025-01-01_2025-12-31") + if (dateRange.includes('_')) { + const [startDate, endDate] = dateRange.split('_'); + const start = new Date(startDate); + const end = new Date(endDate); + end.setHours(23, 59, 59, 999); // Include the end date + return orderDate >= start && orderDate <= end; + } + // Handle year format (e.g., "2026") - calendar year, not rolling 365 days if (dateRange.match(/^\d{4}$/)) { return orderDate.getFullYear() === parseInt(dateRange); @@ -64,6 +158,15 @@ const Analytics = () => { case 'year': // For "This Year" option, show current calendar year return orderDate.getFullYear() === now.getFullYear(); + case 'custom': + // Handle custom date range using state variables + if (customStartDate && customEndDate) { + const start = new Date(customStartDate); + const end = new Date(customEndDate); + end.setHours(23, 59, 59, 999); // Include the end date + return orderDate >= start && orderDate <= end; + } + return true; default: return true; } @@ -95,6 +198,15 @@ const Analytics = () => { return expenseYear === targetYear && expenseQuarter === targetQuarter; } + // Handle custom date range format (e.g., "2025-01-01_2025-12-31") + if (dateRange.includes('_')) { + const [startDate, endDate] = dateRange.split('_'); + const start = new Date(startDate); + const end = new Date(endDate); + end.setHours(23, 59, 59, 999); // Include the end date + return expenseDate >= start && expenseDate <= end; + } + // Handle year format (e.g., "2026") - calendar year, not rolling 365 days if (dateRange.match(/^\d{4}$/)) { return expenseDate.getFullYear() === parseInt(dateRange); @@ -114,6 +226,15 @@ const Analytics = () => { case 'year': // For "This Year" option, show current calendar year return expenseDate.getFullYear() === now.getFullYear(); + case 'custom': + // Handle custom date range using state variables + if (customStartDate && customEndDate) { + const start = new Date(customStartDate); + const end = new Date(customEndDate); + end.setHours(23, 59, 59, 999); // Include the end date + return expenseDate >= start && expenseDate <= end; + } + return true; default: return true; } @@ -304,27 +425,45 @@ const Analytics = () => { value={dateRange} onChange={(e) => setDateRange(e.target.value)} > - - - - - - - - - - - - - - - - - - - - - + {/* Preset Ranges */} + {dateRangeOptions.presets.map(option => ( + + ))} + + {/* Years */} + {dateRangeOptions.years.length > 0 && ( + + {dateRangeOptions.years.map(option => ( + + ))} + + )} + + {/* Quarters */} + {dateRangeOptions.quarters.length > 0 && ( + + {dateRangeOptions.quarters.map(option => ( + + ))} + + )} + + {/* Months */} + {dateRangeOptions.months.length > 0 && ( + + {dateRangeOptions.months.map(option => ( + + ))} + + )} + + + + )} + {/* Key Metrics */}
diff --git a/client/src/pages/ProfitAnalysis.tsx b/client/src/pages/ProfitAnalysis.tsx index f067ab6..2b27aeb 100644 --- a/client/src/pages/ProfitAnalysis.tsx +++ b/client/src/pages/ProfitAnalysis.tsx @@ -8,7 +8,12 @@ const ProfitAnalysis = () => { const { orders } = useSelector((state: RootState) => state.orders); const { products } = useSelector((state: RootState) => state.products); const { expenses } = useSelector((state: RootState) => state.expenses); - const [dateRange, setDateRange] = useState('all'); + const [dateRange, setDateRange] = useState(() => { + // Default to current year + return new Date().getFullYear().toString(); + }); + const [customStartDate, setCustomStartDate] = useState(''); + const [customEndDate, setCustomEndDate] = useState(''); const [selectedView, setSelectedView] = useState<'overview' | 'trends' | 'products' | 'orders'>('overview'); const [expandedOrder, setExpandedOrder] = useState(null); const [orderSortBy, setOrderSortBy] = useState<'date' | 'profit' | 'margin' | 'revenue'>('date'); @@ -146,6 +151,51 @@ const ProfitAnalysis = () => {
+ {/* Custom Date Range Inputs */} + {dateRange === 'custom' && ( +
+
+
+ + setCustomStartDate(e.target.value)} + className="px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+
+ + setCustomEndDate(e.target.value)} + className="px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + /> +
+
+ +
+
+
+ )} + {/* View Selector */}