Refactor UI imports and update component paths
Some checks failed
Build Frontend / build (push) Failing after 7s
Some checks failed
Build Frontend / build (push) Failing after 7s
Replaces imports from 'components/ui' with 'components/common' across the app and dashboard pages, and updates model and API imports to use new paths under 'lib'. Removes redundant authentication checks from several dashboard pages. Adds new dashboard components and utility files, and reorganizes hooks and services into the 'lib' directory for improved structure.
This commit is contained in:
413
components/common/date-picker.tsx
Normal file
413
components/common/date-picker.tsx
Normal file
@@ -0,0 +1,413 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { format, addDays, startOfDay, endOfDay, isSameDay, isWithinInterval, getMonth, getYear, setMonth, setYear } from "date-fns"
|
||||
import { Calendar as CalendarIcon, ChevronLeft, ChevronRight, X } from "lucide-react"
|
||||
import { DateRange } from "react-day-picker"
|
||||
|
||||
import { cn } from "@/lib/utils/styles"
|
||||
import { Button } from "@/components/common/button"
|
||||
import { Calendar } from "@/components/common/calendar"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/common/popover"
|
||||
import { Badge } from "@/components/common/badge"
|
||||
import { Input } from "@/components/common/input"
|
||||
import { Label } from "@/components/common/label"
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/common/select"
|
||||
|
||||
interface DatePickerProps {
|
||||
date?: Date
|
||||
onDateChange?: (date: Date | undefined) => void
|
||||
placeholder?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
interface DateRangePickerProps {
|
||||
dateRange?: DateRange
|
||||
onDateRangeChange?: (range: DateRange | undefined) => void
|
||||
placeholder?: string
|
||||
className?: string
|
||||
showPresets?: boolean
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
interface MonthPickerProps {
|
||||
selectedMonth?: Date
|
||||
onMonthChange?: (date: Date | undefined) => void
|
||||
placeholder?: string
|
||||
className?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
// Single Date Picker
|
||||
export function DatePicker({ date, onDateChange, placeholder = "Pick a date", className }: DatePickerProps) {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-full justify-start text-left font-normal",
|
||||
!date && "text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||
{date ? format(date, "PPP") : placeholder}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Calendar
|
||||
mode="single"
|
||||
selected={date}
|
||||
onSelect={onDateChange}
|
||||
initialFocus
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
// 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 (
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-full justify-start text-left font-normal",
|
||||
!selectedMonth && "text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||
{formatSelectedMonth(selectedMonth)}
|
||||
{selectedMonth && (
|
||||
<div
|
||||
className="ml-auto h-6 w-6 p-0 flex items-center justify-center rounded-sm hover:bg-accent cursor-pointer"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onMonthChange?.(undefined)
|
||||
}}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-4" align="start">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="font-medium">Select Month</h4>
|
||||
<Select value={selectedYear.toString()} onValueChange={handleYearChange}>
|
||||
<SelectTrigger className="w-24">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{years.map((year) => (
|
||||
<SelectItem key={year} value={year.toString()}>
|
||||
{year}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{months.map((month, index) => (
|
||||
<Button
|
||||
key={month}
|
||||
variant={selectedMonthIndex === index ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => handleMonthSelect(index)}
|
||||
className="text-xs"
|
||||
>
|
||||
{month}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
// 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 (
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className={cn(
|
||||
"w-full justify-start text-left font-normal",
|
||||
!dateRange?.from && "text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<CalendarIcon className="mr-2 h-4 w-4" />
|
||||
{formatDateRange(dateRange)}
|
||||
{dateRange?.from && (
|
||||
<div
|
||||
className="ml-auto h-6 w-6 p-0 flex items-center justify-center rounded-sm hover:bg-accent cursor-pointer"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleClear()
|
||||
}}
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<div className="p-3">
|
||||
{showPresets && (
|
||||
<div className="space-y-2 mb-4">
|
||||
<Label className="text-sm font-medium">Quick Select</Label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{presets.map((preset) => (
|
||||
<Button
|
||||
key={preset.label}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="text-xs h-8"
|
||||
onClick={() => handlePresetClick(preset)}
|
||||
>
|
||||
{preset.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Calendar
|
||||
initialFocus
|
||||
mode="range"
|
||||
defaultMonth={dateRange?.from}
|
||||
selected={dateRange}
|
||||
onSelect={onDateRangeChange}
|
||||
numberOfMonths={2}
|
||||
className="rounded-md border"
|
||||
/>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
// Custom Date Range Input Component
|
||||
export function CustomDateRangeInput({
|
||||
dateRange,
|
||||
onDateRangeChange,
|
||||
className
|
||||
}: DateRangePickerProps) {
|
||||
const [fromDate, setFromDate] = React.useState<string>("")
|
||||
const [toDate, setToDate] = React.useState<string>("")
|
||||
|
||||
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 (
|
||||
<div className={cn("flex items-center gap-2", className)}>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="from-date" className="text-sm whitespace-nowrap">From:</Label>
|
||||
<Input
|
||||
id="from-date"
|
||||
type="date"
|
||||
value={fromDate}
|
||||
onChange={(e) => handleFromDateChange(e.target.value)}
|
||||
className="w-32"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label htmlFor="to-date" className="text-sm whitespace-nowrap">To:</Label>
|
||||
<Input
|
||||
id="to-date"
|
||||
type="date"
|
||||
value={toDate}
|
||||
onChange={(e) => handleToDateChange(e.target.value)}
|
||||
className="w-32"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 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 (
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="secondary" className="text-xs">
|
||||
{daysDiff} day{daysDiff !== 1 ? 's' : ''}
|
||||
</Badge>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
{format(dateRange.from, "MMM dd")}
|
||||
{dateRange.to && !isSameDay(dateRange.from, dateRange.to) && (
|
||||
<> - {format(dateRange.to, "MMM dd, yyyy")}</>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user