diff --git a/app/dashboard/stock/page.tsx b/app/dashboard/stock/page.tsx index 5cbb8d8..9eb0075 100644 --- a/app/dashboard/stock/page.tsx +++ b/app/dashboard/stock/page.tsx @@ -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([]); @@ -50,7 +55,15 @@ export default function StockManagementPage() { const [selectedProducts, setSelectedProducts] = useState([]); const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false); const [bulkAction, setBulkAction] = useState<'enable' | 'disable' | null>(null); + + // Export state const [exportDate, setExportDate] = useState(new Date().toISOString().split('T')[0]); + const [exportDateRange, setExportDateRange] = useState({ + from: startOfDay(addDays(new Date(), -6)), + to: endOfDay(new Date()) + }); + const [selectedMonth, setSelectedMonth] = useState(new Date()); + const [reportType, setReportType] = useState('daily'); const [isExporting, setIsExporting] = useState(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)} /> - - - - - -
- - setExportDate(e.target.value)} - className="w-full" - /> -
-
-
+ + + setReportType('daily')}> + Daily Report + + setReportType('weekly')}> + Weekly Report + + setReportType('monthly')}> + Monthly Report + + setReportType('custom')}> + Custom Range + + + + + {/* Date Selection based on report type */} + {reportType === 'daily' && ( + setExportDate(date ? date.toISOString().split('T')[0] : '')} + placeholder="Select export date" + className="w-auto" + /> + )} + + {(reportType === 'weekly' || reportType === 'custom') && ( + + )} + + {reportType === 'monthly' && ( + setSelectedMonth(date || new Date())} + placeholder="Select month" + className="w-auto" + /> + )} + + + + + + + ) +} + +// Month Picker Component +export function MonthPicker({ selectedMonth, onMonthChange, placeholder = "Pick a month", className, disabled = false }: MonthPickerProps) { + const [isOpen, setIsOpen] = React.useState(false) + const [selectedYear, setSelectedYear] = React.useState(selectedMonth ? getYear(selectedMonth) : new Date().getFullYear()) + const [selectedMonthIndex, setSelectedMonthIndex] = React.useState(selectedMonth ? getMonth(selectedMonth) : new Date().getMonth()) + + const months = [ + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + ] + + const years = Array.from({ length: 10 }, (_, i) => new Date().getFullYear() - 5 + i) + + React.useEffect(() => { + if (selectedMonth) { + setSelectedYear(getYear(selectedMonth)) + setSelectedMonthIndex(getMonth(selectedMonth)) + } + }, [selectedMonth]) + + const handleMonthSelect = (monthIndex: number) => { + const newDate = new Date(selectedYear, monthIndex, 1) + onMonthChange?.(newDate) + setIsOpen(false) + } + + const handleYearChange = (year: string) => { + const newYear = parseInt(year) + setSelectedYear(newYear) + if (selectedMonth) { + const newDate = new Date(newYear, selectedMonthIndex, 1) + onMonthChange?.(newDate) + } + } + + const formatSelectedMonth = (date?: Date) => { + if (!date) return placeholder + return format(date, "MMMM yyyy") + } + + return ( + + + + )} + + + +
+
+

Select Month

+ +
+ +
+ {months.map((month, index) => ( + + ))} +
+
+
+
+ ) +} + +// Date Range Picker with Presets +export function DateRangePicker({ + dateRange, + onDateRangeChange, + placeholder = "Pick a date range", + className, + showPresets = true, + disabled = false +}: DateRangePickerProps) { + const [isOpen, setIsOpen] = React.useState(false) + + const presets = [ + { + label: "Today", + value: { + from: startOfDay(new Date()), + to: endOfDay(new Date()) + } + }, + { + label: "Yesterday", + value: { + from: startOfDay(addDays(new Date(), -1)), + to: endOfDay(addDays(new Date(), -1)) + } + }, + { + label: "Last 7 days", + value: { + from: startOfDay(addDays(new Date(), -6)), + to: endOfDay(new Date()) + } + }, + { + label: "Last 30 days", + value: { + from: startOfDay(addDays(new Date(), -29)), + to: endOfDay(new Date()) + } + }, + { + label: "This month", + value: { + from: startOfDay(new Date(new Date().getFullYear(), new Date().getMonth(), 1)), + to: endOfDay(new Date()) + } + }, + { + label: "Last month", + value: { + from: startOfDay(new Date(new Date().getFullYear(), new Date().getMonth() - 1, 1)), + to: endOfDay(new Date(new Date().getFullYear(), new Date().getMonth(), 0)) + } + } + ] + + const handlePresetClick = (preset: typeof presets[0]) => { + onDateRangeChange?.(preset.value) + setIsOpen(false) + } + + const handleClear = () => { + onDateRangeChange?.(undefined) + setIsOpen(false) + } + + const formatDateRange = (range: DateRange | undefined) => { + if (!range?.from) return placeholder + + if (!range.to) { + return format(range.from, "PPP") + } + + if (isSameDay(range.from, range.to)) { + return format(range.from, "PPP") + } + + return `${format(range.from, "MMM dd")} - ${format(range.to, "MMM dd, yyyy")}` + } + + return ( + + + + )} + + + +
+ {showPresets && ( +
+ +
+ {presets.map((preset) => ( + + ))} +
+
+ )} + +
+
+
+ ) +} + +// Custom Date Range Input Component +export function CustomDateRangeInput({ + dateRange, + onDateRangeChange, + className +}: DateRangePickerProps) { + const [fromDate, setFromDate] = React.useState("") + const [toDate, setToDate] = React.useState("") + + React.useEffect(() => { + if (dateRange?.from) { + setFromDate(format(dateRange.from, "yyyy-MM-dd")) + } + if (dateRange?.to) { + setToDate(format(dateRange.to, "yyyy-MM-dd")) + } + }, [dateRange]) + + const handleFromDateChange = (value: string) => { + setFromDate(value) + const from = value ? new Date(value) : undefined + const to = toDate ? new Date(toDate) : dateRange?.to + + if (from && to && from > to) { + // If from date is after to date, adjust to date + onDateRangeChange?.({ from, to: from }) + setToDate(value) + } else { + onDateRangeChange?.({ from, to }) + } + } + + const handleToDateChange = (value: string) => { + setToDate(value) + const from = fromDate ? new Date(fromDate) : dateRange?.from + const to = value ? new Date(value) : undefined + + if (from && to && from > to) { + // If to date is before from date, adjust from date + onDateRangeChange?.({ from: to, to }) + setFromDate(value) + } else { + onDateRangeChange?.({ from, to }) + } + } + + return ( +
+
+ + handleFromDateChange(e.target.value)} + className="w-32" + /> +
+
+ + handleToDateChange(e.target.value)} + className="w-32" + /> +
+
+ ) +} + +// Date Range Display Component +export function DateRangeDisplay({ dateRange }: { dateRange?: DateRange }) { + if (!dateRange?.from) return null + + const daysDiff = dateRange.to + ? Math.ceil((dateRange.to.getTime() - dateRange.from.getTime()) / (1000 * 60 * 60 * 24)) + 1 + : 1 + + return ( +
+ + {daysDiff} day{daysDiff !== 1 ? 's' : ''} + + + {format(dateRange.from, "MMM dd")} + {dateRange.to && !isSameDay(dateRange.from, dateRange.to) && ( + <> - {format(dateRange.to, "MMM dd, yyyy")} + )} + +
+ ) +} \ No newline at end of file diff --git a/config/quotes.ts b/config/quotes.ts index 9268b9f..85ab63c 100644 --- a/config/quotes.ts +++ b/config/quotes.ts @@ -1,8 +1,3 @@ -/** - * Business motivation quotes for the dashboard - * Collection of quotes from successful entrepreneurs and business leaders - */ - export interface Quote { text: string; author: string; @@ -50,6 +45,38 @@ export const businessQuotes: Quote[] = [ { text: "If you want to achieve greatness stop asking for permission.", author: "Anonymous" }, { text: "Things work out best for those who make the best of how things work out.", author: "John Wooden" }, { text: "The most valuable businesses of coming decades will be built by entrepreneurs who seek to empower people rather than try to make them obsolete.", author: "Peter Thiel" }, + + // Additional quotes - Vision and leadership + { text: "Leadership is the capacity to translate vision into reality.", author: "Warren Bennis" }, + { text: "A goal without a plan is just a wish.", author: "Antoine de Saint-Exupéry" }, + { text: "Good business leaders create a vision, articulate the vision, passionately own the vision, and relentlessly drive it to completion.", author: "Jack Welch" }, + + // Additional quotes - Risk, failure, and learning + { text: "Fail often so you can succeed sooner.", author: "Tom Kelley" }, + { text: "Don’t worry about failure; you only have to be right once.", author: "Drew Houston" }, + { text: "In the middle of difficulty lies opportunity.", author: "Albert Einstein" }, + { text: "Risk more than others think is safe. Dream more than others think is practical.", author: "Howard Schultz" }, + + // Additional quotes - Action and hustle + { text: "Ideas are easy. Implementation is hard.", author: "Guy Kawasaki" }, + { text: "Success usually comes to those who are too busy to be looking for it.", author: "Henry David Thoreau" }, + { text: "Done is better than perfect.", author: "Sheryl Sandberg" }, + { text: "Action is the foundational key to all success.", author: "Pablo Picasso" }, + + // Additional quotes - Customer and product + { text: "People don't buy what you do; they buy why you do it.", author: "Simon Sinek" }, + { text: "The customer is the most important part of the production line.", author: "W. Edwards Deming" }, + { text: "Make something people want and sell that, or be someone people need and sell yourself.", author: "Naval Ravikant" }, + + // Additional quotes - Resilience and mindset + { text: "Success is walking from failure to failure with no loss of enthusiasm.", author: "Winston Churchill" }, + { text: "Whether you think you can or you think you can’t, you’re right.", author: "Henry Ford" }, + { text: "Strength and growth come only through continuous effort and struggle.", author: "Napoleon Hill" }, + { text: "Believe you can and you're halfway there.", author: "Theodore Roosevelt" }, + + // Additional quotes - Money and value + { text: "Try not to become a man of success, but rather try to become a man of value.", author: "Albert Einstein" }, + { text: "Price is what you pay. Value is what you get.", author: "Warren Buffett" }, ]; // For backward compatibility with existing code @@ -88,4 +115,4 @@ export function getQuotesByTheme(keyword: string): Quote[] { return businessQuotes.filter(quote => quote.text.toLowerCase().includes(keyword.toLowerCase()) ); -} \ No newline at end of file +}