148 lines
4.3 KiB
TypeScript

"use client";
import { useState, useMemo } from 'react';
interface RecordDatePickerProps {
value: string | null;
dailyCounts: Record<string, number>;
onChange: (date: string) => void;
}
export default function RecordDatePicker({ value, dailyCounts, onChange }: RecordDatePickerProps) {
const today = new Date();
const initialDate = value ? new Date(value) : today;
const [currentYear, setCurrentYear] = useState(initialDate.getFullYear());
const [currentMonth, setCurrentMonth] = useState(initialDate.getMonth());
const weekDays = ['日', '一', '二', '三', '四', '五', '六'];
const calendarDays = useMemo(() => {
const year = currentYear;
const month = currentMonth;
const firstDayOfMonth = new Date(year, month, 1);
const lastDayOfMonth = new Date(year, month + 1, 0);
const daysInMonth = lastDayOfMonth.getDate();
const startDayIndex = firstDayOfMonth.getDay();
const totalCells = Math.ceil((startDayIndex + daysInMonth) / 7) * 7;
const days = [];
const startDate = new Date(year, month, 1 - startDayIndex);
for (let i = 0; i < totalCells; i++) {
const date = new Date(startDate);
date.setDate(startDate.getDate() + i);
const dateString = `${date.getFullYear()}-${(date.getMonth() + 1)
.toString()
.padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
days.push({
date,
day: date.getDate(),
isCurrentMonth: date.getMonth() === month,
dateString,
count: dailyCounts[dateString] || 0
});
}
return days;
}, [currentYear, currentMonth, dailyCounts]);
const maxCount = useMemo(() => {
let max = 0;
calendarDays.forEach((day) => {
if (day.count > max) {
max = day.count;
}
});
return max;
}, [calendarDays]);
const selectDay = (day: { date: Date; dateString: string; isCurrentMonth: boolean }) => {
if (!day.isCurrentMonth) {
setCurrentYear(day.date.getFullYear());
setCurrentMonth(day.date.getMonth());
}
onChange(day.dateString);
};
const getDayStyle = (day: { dateString: string; count: number }) => {
const styles: React.CSSProperties = {};
if (value === day.dateString) {
styles.outline = '2px solid #2563EB';
}
if (day.count > 0) {
const ratio = maxCount > 0 ? day.count / maxCount : 0;
const opacity = 0.3 + ratio * 0.7;
styles.backgroundColor = `rgba(16, 185, 129, ${opacity})`;
styles.color = opacity > 0.6 ? 'white' : 'black';
} else {
styles.backgroundColor = 'transparent';
styles.color = 'inherit';
}
return styles;
};
const prevMonth = () => {
if (currentMonth === 0) {
setCurrentYear(currentYear - 1);
setCurrentMonth(11);
} else {
setCurrentMonth(currentMonth - 1);
}
};
const nextMonth = () => {
if (currentMonth === 11) {
setCurrentYear(currentYear + 1);
setCurrentMonth(0);
} else {
setCurrentMonth(currentMonth + 1);
}
};
return (
<div className="p-4 bg-white rounded shadow">
{/* 月份切换头部 */}
<div className="flex items-center justify-between mb-2">
<button onClick={prevMonth} className="p-2 rounded hover:bg-gray-200">
&lt;
</button>
<div className="font-semibold text-lg">
{currentYear} - {(currentMonth + 1).toString().padStart(2, '0')}
</div>
<button onClick={nextMonth} className="p-2 rounded hover:bg-gray-200">
&gt;
</button>
</div>
{/* 星期标题 */}
<div className="grid grid-cols-7 gap-1 text-center text-sm font-medium text-gray-600 mb-1">
{weekDays.map((day) => (
<div key={day}>{day}</div>
))}
</div>
{/* 日历网格 */}
<div className="grid grid-cols-7 gap-1">
{calendarDays.map((day, index) => (
<div
key={index}
className={`p-2 my-1 rounded cursor-pointer text-sm flex items-center justify-center ${
!day.isCurrentMonth ? 'opacity-50' : ''
}`}
style={getDayStyle(day)}
onClick={() => selectDay(day)}
>
<div className="relative">
<span>{day.day}</span>
</div>
</div>
))}
</div>
</div>
);
}