Fix review findings: safe date parsing, useCallback discipline, dead class cleanup
W-01: Wrap handlePrev/handleNext/handleDayClick in useCallback W-02: Use date-fns parse() instead of new Date() for timezone-safe parsing W-03: Change default firstDayOfWeek from 1 to 0 to match CalendarPage S-01: Use format(day, 'yyyy-MM-dd') as React key instead of toISOString() S-02: Remove dead Tailwind color classes overridden by inline styles Perf: Guard setSelectedDate with comparison to skip no-op re-renders Perf: Memoize selectedDateObj via useMemo to avoid re-parsing each render Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a5ac047b0b
commit
b939843249
@ -1,8 +1,8 @@
|
|||||||
import { useState, useEffect, useMemo, memo } from 'react';
|
import { useState, useEffect, useMemo, useCallback, memo } from 'react';
|
||||||
import {
|
import {
|
||||||
startOfMonth, endOfMonth, startOfWeek, endOfWeek,
|
startOfMonth, endOfMonth, startOfWeek, endOfWeek,
|
||||||
eachDayOfInterval, format, isSameDay, isSameMonth, isToday,
|
eachDayOfInterval, format, isSameDay, isSameMonth, isToday,
|
||||||
addMonths, subMonths,
|
addMonths, subMonths, parse,
|
||||||
} from 'date-fns';
|
} from 'date-fns';
|
||||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@ -30,10 +30,17 @@ function getOrderedLabels(firstDay: number) {
|
|||||||
const MiniCalendar = memo(function MiniCalendar({
|
const MiniCalendar = memo(function MiniCalendar({
|
||||||
onDateClick,
|
onDateClick,
|
||||||
currentDate,
|
currentDate,
|
||||||
firstDayOfWeek = 1,
|
firstDayOfWeek = 0,
|
||||||
}: MiniCalendarProps) {
|
}: MiniCalendarProps) {
|
||||||
|
const REF_DATE = useMemo(() => new Date(), []);
|
||||||
|
|
||||||
|
const parseDate = useCallback(
|
||||||
|
(dateStr: string) => parse(dateStr, 'yyyy-MM-dd', REF_DATE),
|
||||||
|
[REF_DATE]
|
||||||
|
);
|
||||||
|
|
||||||
const [displayedMonth, setDisplayedMonth] = useState(() =>
|
const [displayedMonth, setDisplayedMonth] = useState(() =>
|
||||||
currentDate ? startOfMonth(new Date(currentDate)) : startOfMonth(new Date())
|
currentDate ? startOfMonth(parseDate(currentDate)) : startOfMonth(new Date())
|
||||||
);
|
);
|
||||||
const [selectedDate, setSelectedDate] = useState<string | null>(
|
const [selectedDate, setSelectedDate] = useState<string | null>(
|
||||||
currentDate ?? null
|
currentDate ?? null
|
||||||
@ -42,12 +49,12 @@ const MiniCalendar = memo(function MiniCalendar({
|
|||||||
// Sync displayed month when main calendar navigates across months
|
// Sync displayed month when main calendar navigates across months
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!currentDate) return;
|
if (!currentDate) return;
|
||||||
const incoming = startOfMonth(new Date(currentDate));
|
const incoming = startOfMonth(parseDate(currentDate));
|
||||||
setDisplayedMonth((prev) =>
|
setDisplayedMonth((prev) =>
|
||||||
prev.getTime() === incoming.getTime() ? prev : incoming
|
prev.getTime() === incoming.getTime() ? prev : incoming
|
||||||
);
|
);
|
||||||
setSelectedDate(currentDate);
|
setSelectedDate((prev) => prev === currentDate ? prev : currentDate);
|
||||||
}, [currentDate]);
|
}, [currentDate, parseDate]);
|
||||||
|
|
||||||
const days = useMemo(
|
const days = useMemo(
|
||||||
() => buildGrid(displayedMonth, firstDayOfWeek),
|
() => buildGrid(displayedMonth, firstDayOfWeek),
|
||||||
@ -59,10 +66,10 @@ const MiniCalendar = memo(function MiniCalendar({
|
|||||||
[firstDayOfWeek]
|
[firstDayOfWeek]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handlePrev = () => setDisplayedMonth((m) => subMonths(m, 1));
|
const handlePrev = useCallback(() => setDisplayedMonth((m) => subMonths(m, 1)), []);
|
||||||
const handleNext = () => setDisplayedMonth((m) => addMonths(m, 1));
|
const handleNext = useCallback(() => setDisplayedMonth((m) => addMonths(m, 1)), []);
|
||||||
|
|
||||||
const handleDayClick = (day: Date) => {
|
const handleDayClick = useCallback((day: Date) => {
|
||||||
const dateStr = format(day, 'yyyy-MM-dd');
|
const dateStr = format(day, 'yyyy-MM-dd');
|
||||||
setSelectedDate(dateStr);
|
setSelectedDate(dateStr);
|
||||||
// If clicking a day in another month, also shift the displayed month
|
// If clicking a day in another month, also shift the displayed month
|
||||||
@ -70,9 +77,12 @@ const MiniCalendar = memo(function MiniCalendar({
|
|||||||
setDisplayedMonth(startOfMonth(day));
|
setDisplayedMonth(startOfMonth(day));
|
||||||
}
|
}
|
||||||
onDateClick(dateStr);
|
onDateClick(dateStr);
|
||||||
};
|
}, [displayedMonth, onDateClick]);
|
||||||
|
|
||||||
const selectedDateObj = selectedDate ? new Date(selectedDate) : null;
|
const selectedDateObj = useMemo(
|
||||||
|
() => selectedDate ? parseDate(selectedDate) : null,
|
||||||
|
[selectedDate, parseDate]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="px-3 pt-3 pb-2 max-w-[280px] mx-auto">
|
<div className="px-3 pt-3 pb-2 max-w-[280px] mx-auto">
|
||||||
@ -120,15 +130,15 @@ const MiniCalendar = memo(function MiniCalendar({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={day.toISOString()}
|
key={format(day, 'yyyy-MM-dd')}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleDayClick(day)}
|
onClick={() => handleDayClick(day)}
|
||||||
className={[
|
className={[
|
||||||
'h-7 text-xs flex items-center justify-center rounded-md transition-colors duration-100',
|
'h-7 text-xs flex items-center justify-center rounded-md transition-colors duration-100',
|
||||||
isSelected
|
isSelected
|
||||||
? 'bg-accent text-accent-foreground font-medium'
|
? 'font-medium'
|
||||||
: today
|
: today
|
||||||
? 'bg-accent/20 text-accent font-semibold'
|
? 'font-semibold'
|
||||||
: isCurrentMonth
|
: isCurrentMonth
|
||||||
? 'text-foreground hover:bg-accent/10'
|
? 'text-foreground hover:bg-accent/10'
|
||||||
: 'text-muted-foreground/40 hover:bg-accent/10',
|
: 'text-muted-foreground/40 hover:bg-accent/10',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user