148 lines
4.5 KiB
TypeScript
148 lines
4.5 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 dark:bg-gray-800 rounded shadow">
|
|
{/* 月份切换头部 */}
|
|
<div className="flex items-center justify-between mb-2">
|
|
<button onClick={prevMonth} className="p-2 rounded hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-900 dark:text-white">
|
|
<
|
|
</button>
|
|
<div className="font-semibold text-lg text-gray-900 dark:text-white">
|
|
{currentYear} - {(currentMonth + 1).toString().padStart(2, '0')}
|
|
</div>
|
|
<button onClick={nextMonth} className="p-2 rounded hover:bg-gray-200 dark:hover:bg-gray-700 text-gray-900 dark:text-white">
|
|
>
|
|
</button>
|
|
</div>
|
|
|
|
{/* 星期标题 */}
|
|
<div className="grid grid-cols-7 gap-1 text-center text-sm font-medium text-gray-600 dark:text-gray-400 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>
|
|
);
|
|
}
|