mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
183fe0a93c
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
149 lines
6.5 KiB
TypeScript
149 lines
6.5 KiB
TypeScript
import { useState, useMemo } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
import { CalendarClock } from "lucide-react";
|
|
import { addDays, addMonths, addYears, differenceInDays, differenceInBusinessDays, format, parseISO } from "date-fns";
|
|
|
|
const todayStr = () => format(new Date(), "yyyy-MM-dd");
|
|
|
|
export function DateCalculatorPopover() {
|
|
// Difference tab
|
|
const [from, setFrom] = useState(todayStr());
|
|
const [to, setTo] = useState(todayStr());
|
|
|
|
// Add/subtract tab
|
|
const [base, setBase] = useState(todayStr());
|
|
const [amount, setAmount] = useState<number>(30);
|
|
const [unit, setUnit] = useState<"days" | "months" | "years">("days");
|
|
const [direction, setDirection] = useState<"add" | "subtract">("add");
|
|
|
|
const diff = useMemo(() => {
|
|
try {
|
|
const f = parseISO(from);
|
|
const t = parseISO(to);
|
|
return {
|
|
days: differenceInDays(t, f),
|
|
business: differenceInBusinessDays(t, f),
|
|
weeks: Math.floor(differenceInDays(t, f) / 7),
|
|
};
|
|
} catch {
|
|
return { days: 0, business: 0, weeks: 0 };
|
|
}
|
|
}, [from, to]);
|
|
|
|
const result = useMemo(() => {
|
|
try {
|
|
const b = parseISO(base);
|
|
const n = direction === "subtract" ? -amount : amount;
|
|
const d = unit === "days" ? addDays(b, n) : unit === "months" ? addMonths(b, n) : addYears(b, n);
|
|
return format(d, "EEEE, MMMM d, yyyy");
|
|
} catch {
|
|
return "—";
|
|
}
|
|
}, [base, amount, unit, direction]);
|
|
|
|
return (
|
|
<Popover>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<PopoverTrigger asChild>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-7 w-7 p-0 text-muted-foreground hover:text-foreground"
|
|
>
|
|
<CalendarClock className="h-3.5 w-3.5" />
|
|
</Button>
|
|
</PopoverTrigger>
|
|
</TooltipTrigger>
|
|
<TooltipContent>Date Calculator</TooltipContent>
|
|
</Tooltip>
|
|
<PopoverContent align="end" className="w-80 p-3">
|
|
<div className="space-y-3">
|
|
<h4 className="text-[13px] font-semibold">Date Calculator</h4>
|
|
|
|
<Tabs defaultValue="diff">
|
|
<TabsList className="grid w-full grid-cols-2 h-8">
|
|
<TabsTrigger value="diff" className="text-[12px]">Difference</TabsTrigger>
|
|
<TabsTrigger value="add" className="text-[12px]">Add / Subtract</TabsTrigger>
|
|
</TabsList>
|
|
|
|
<TabsContent value="diff" className="space-y-2 pt-3">
|
|
<div className="space-y-1.5">
|
|
<Label className="text-[11px]">From</Label>
|
|
<Input type="date" value={from} onChange={(e) => setFrom(e.target.value)} className="h-8 text-[12px]" />
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-[11px]">To</Label>
|
|
<Input type="date" value={to} onChange={(e) => setTo(e.target.value)} className="h-8 text-[12px]" />
|
|
</div>
|
|
<div className="bg-muted/50 rounded-lg p-2.5 space-y-1 mt-2">
|
|
<div className="flex justify-between text-[12px]">
|
|
<span className="text-muted-foreground">Calendar days</span>
|
|
<span className="font-mono font-semibold">{diff.days}</span>
|
|
</div>
|
|
<div className="flex justify-between text-[12px]">
|
|
<span className="text-muted-foreground">Business days</span>
|
|
<span className="font-mono font-semibold">{diff.business}</span>
|
|
</div>
|
|
<div className="flex justify-between text-[12px]">
|
|
<span className="text-muted-foreground">Weeks</span>
|
|
<span className="font-mono font-semibold">{diff.weeks}</span>
|
|
</div>
|
|
</div>
|
|
</TabsContent>
|
|
|
|
<TabsContent value="add" className="space-y-2 pt-3">
|
|
<div className="space-y-1.5">
|
|
<Label className="text-[11px]">Start date</Label>
|
|
<Input type="date" value={base} onChange={(e) => setBase(e.target.value)} className="h-8 text-[12px]" />
|
|
</div>
|
|
<div className="grid grid-cols-3 gap-2">
|
|
<div className="space-y-1.5">
|
|
<Label className="text-[11px]">Action</Label>
|
|
<Select value={direction} onValueChange={(v) => setDirection(v as any)}>
|
|
<SelectTrigger className="h-8 text-[12px]"><SelectValue /></SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="add" className="text-[12px]">Add</SelectItem>
|
|
<SelectItem value="subtract" className="text-[12px]">Subtract</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-[11px]">Amount</Label>
|
|
<Input
|
|
type="number"
|
|
value={amount}
|
|
onChange={(e) => setAmount(parseInt(e.target.value) || 0)}
|
|
className="h-8 text-[12px]"
|
|
/>
|
|
</div>
|
|
<div className="space-y-1.5">
|
|
<Label className="text-[11px]">Unit</Label>
|
|
<Select value={unit} onValueChange={(v) => setUnit(v as any)}>
|
|
<SelectTrigger className="h-8 text-[12px]"><SelectValue /></SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="days" className="text-[12px]">Days</SelectItem>
|
|
<SelectItem value="months" className="text-[12px]">Months</SelectItem>
|
|
<SelectItem value="years" className="text-[12px]">Years</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
<div className="bg-muted/50 rounded-lg p-2.5 mt-2">
|
|
<p className="text-[11px] text-muted-foreground">Result</p>
|
|
<p className="text-[13px] font-semibold mt-0.5">{result}</p>
|
|
</div>
|
|
</TabsContent>
|
|
</Tabs>
|
|
</div>
|
|
</PopoverContent>
|
|
</Popover>
|
|
);
|
|
}
|