Add flexible date pickers and export options to stock dashboard

Introduces a reusable date picker component with support for single date, date range, and month selection. Updates the stock management page to allow exporting reports by daily, weekly, monthly, or custom date ranges using the new pickers. Refactors promotion form to use the new date picker for start and end dates. Adds more business quotes to the quotes config.
This commit is contained in:
NotII
2025-07-30 00:38:25 +02:00
parent 48cfd45fb1
commit 1b51f29c24
5 changed files with 575 additions and 39 deletions

View File

@@ -33,6 +33,9 @@ import { Product } from "@/models/products";
import { Package, RefreshCw, ChevronDown, CheckSquare, XSquare, Boxes, Download, Calendar } from "lucide-react";
import { clientFetch } from "@/lib/api";
import { toast } from "sonner";
import { DatePicker, DateRangePicker, DateRangeDisplay, MonthPicker } from "@/components/ui/date-picker";
import { DateRange } from "react-day-picker";
import { addDays, startOfDay, endOfDay, format, isSameDay } from "date-fns";
interface StockData {
currentStock: number;
@@ -40,6 +43,8 @@ interface StockData {
lowStockThreshold?: number;
}
type ReportType = 'daily' | 'weekly' | 'monthly' | 'custom';
export default function StockManagementPage() {
const router = useRouter();
const [products, setProducts] = useState<Product[]>([]);
@@ -50,7 +55,15 @@ export default function StockManagementPage() {
const [selectedProducts, setSelectedProducts] = useState<string[]>([]);
const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false);
const [bulkAction, setBulkAction] = useState<'enable' | 'disable' | null>(null);
// Export state
const [exportDate, setExportDate] = useState<string>(new Date().toISOString().split('T')[0]);
const [exportDateRange, setExportDateRange] = useState<DateRange | undefined>({
from: startOfDay(addDays(new Date(), -6)),
to: endOfDay(new Date())
});
const [selectedMonth, setSelectedMonth] = useState<Date>(new Date());
const [reportType, setReportType] = useState<ReportType>('daily');
const [isExporting, setIsExporting] = useState<boolean>(false);
useEffect(() => {
@@ -259,7 +272,47 @@ export default function StockManagementPage() {
const handleExportStock = async () => {
setIsExporting(true);
try {
const response = await clientFetch(`/api/analytics/daily-stock-report?date=${exportDate}`);
let response;
let filename;
switch (reportType) {
case 'daily':
response = await clientFetch(`/api/analytics/daily-stock-report?date=${exportDate}`);
filename = `daily-stock-report-${exportDate}.csv`;
break;
case 'weekly':
if (!exportDateRange?.from) {
toast.error('Please select a date range for weekly report');
return;
}
const weekStart = format(exportDateRange.from, 'yyyy-MM-dd');
response = await clientFetch(`/api/analytics/weekly-stock-report?weekStart=${weekStart}`);
filename = `weekly-stock-report-${weekStart}.csv`;
break;
case 'monthly':
const year = selectedMonth.getFullYear();
const month = selectedMonth.getMonth() + 1;
response = await clientFetch(`/api/analytics/monthly-stock-report?year=${year}&month=${month}`);
filename = `monthly-stock-report-${year}-${month.toString().padStart(2, '0')}.csv`;
break;
case 'custom':
if (!exportDateRange?.from || !exportDateRange?.to) {
toast.error('Please select a date range for custom report');
return;
}
const startDate = format(exportDateRange.from, 'yyyy-MM-dd');
const endDate = format(exportDateRange.to, 'yyyy-MM-dd');
response = await clientFetch(`/api/analytics/daily-stock-report?startDate=${startDate}&endDate=${endDate}`);
filename = `custom-stock-report-${startDate}-to-${endDate}.csv`;
break;
default:
toast.error('Invalid report type');
return;
}
if (!response || !response.products) {
throw new Error('No data received from server');
@@ -297,14 +350,19 @@ export default function StockManagementPage() {
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', `daily-stock-report-${exportDate}.csv`);
link.setAttribute('download', filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
toast.success(`Stock report for ${exportDate} exported successfully`);
const periodText = reportType === 'daily' ? exportDate :
reportType === 'weekly' ? `week starting ${format(exportDateRange?.from || new Date(), 'MMM dd')}` :
reportType === 'monthly' ? `${response.monthName || 'current month'}` :
`${format(exportDateRange?.from || new Date(), 'MMM dd')} to ${format(exportDateRange?.to || new Date(), 'MMM dd')}`;
toast.success(`${reportType.charAt(0).toUpperCase() + reportType.slice(1)} stock report for ${periodText} exported successfully`);
} catch (error) {
console.error('Error exporting stock report:', error);
toast.error('Failed to export stock report');
@@ -348,28 +406,60 @@ export default function StockManagementPage() {
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className="gap-2"
>
{/* Report Type Selector */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="gap-2">
<Calendar className="h-4 w-4" />
{exportDate}
{reportType.charAt(0).toUpperCase() + reportType.slice(1)} Report
<ChevronDown className="h-4 w-4" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-3" align="end">
<div className="space-y-2">
<label className="text-sm font-medium">Select Date for Export</label>
<Input
type="date"
value={exportDate}
onChange={(e) => setExportDate(e.target.value)}
className="w-full"
/>
</div>
</PopoverContent>
</Popover>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={() => setReportType('daily')}>
Daily Report
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setReportType('weekly')}>
Weekly Report
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setReportType('monthly')}>
Monthly Report
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setReportType('custom')}>
Custom Range
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
{/* Date Selection based on report type */}
{reportType === 'daily' && (
<DatePicker
date={exportDate ? new Date(exportDate) : undefined}
onDateChange={(date) => setExportDate(date ? date.toISOString().split('T')[0] : '')}
placeholder="Select export date"
className="w-auto"
/>
)}
{(reportType === 'weekly' || reportType === 'custom') && (
<DateRangePicker
dateRange={exportDateRange}
onDateRangeChange={setExportDateRange}
placeholder="Select date range"
className="w-auto"
/>
)}
{reportType === 'monthly' && (
<MonthPicker
selectedMonth={selectedMonth}
onMonthChange={(date) => setSelectedMonth(date || new Date())}
placeholder="Select month"
className="w-auto"
/>
)}
<Button
variant="outline"
onClick={handleExportStock}
@@ -383,6 +473,7 @@ export default function StockManagementPage() {
)}
{isExporting ? 'Exporting...' : 'Export CSV'}
</Button>
{selectedProducts.length > 0 && (
<DropdownMenu>
<DropdownMenuTrigger asChild>