Backend: .NET 10 + PostgreSQL + EF Core + JWT + SignalR Frontend patient: React 19 + TypeScript + Vite (mobile H5) Frontend doctor: React 19 + TypeScript + Vite (desktop web)
113 lines
3.8 KiB
TypeScript
113 lines
3.8 KiB
TypeScript
import { useState, useMemo } from 'react';
|
||
import { PageHeader } from '@/components/layout/PageHeader';
|
||
import { Card } from '@/components/common/Card';
|
||
import type { CalendarDay } from '@/types';
|
||
import dayjs from 'dayjs';
|
||
import styles from './HealthCalendarPage.module.css';
|
||
|
||
const MARKER_COLORS: Record<string, string> = {
|
||
medication_taken: '#10B981',
|
||
medication_missed: '#EF4444',
|
||
follow_up: '#F59E0B',
|
||
measurement: '#2563EB',
|
||
};
|
||
|
||
export function HealthCalendarPage() {
|
||
const [currentDate, setCurrentDate] = useState(dayjs());
|
||
|
||
const calendarDays = useMemo(() => {
|
||
const startOfMonth = currentDate.startOf('month');
|
||
const endOfMonth = currentDate.endOf('month');
|
||
const startDay = startOfMonth.day();
|
||
const days: CalendarDay[] = [];
|
||
|
||
const today = dayjs().format('YYYY-MM-DD');
|
||
|
||
for (let i = startDay - 1; i >= 0; i--) {
|
||
const d = startOfMonth.subtract(i + 1, 'day');
|
||
days.push({
|
||
date: d.format('YYYY-MM-DD'),
|
||
year: d.year(),
|
||
month: d.month() + 1,
|
||
day: d.date(),
|
||
isCurrentMonth: false,
|
||
isToday: d.format('YYYY-MM-DD') === today,
|
||
markers: [],
|
||
});
|
||
}
|
||
|
||
for (let d = startOfMonth; d.isBefore(endOfMonth) || d.isSame(endOfMonth, 'day'); d = d.add(1, 'day')) {
|
||
const dateStr = d.format('YYYY-MM-DD');
|
||
const markers: CalendarDay['markers'] = [];
|
||
|
||
// Calendar markers would be populated from real API data
|
||
|
||
days.push({
|
||
date: dateStr,
|
||
year: d.year(),
|
||
month: d.month() + 1,
|
||
day: d.date(),
|
||
isCurrentMonth: true,
|
||
isToday: dateStr === today,
|
||
markers,
|
||
});
|
||
}
|
||
|
||
return days;
|
||
}, [currentDate]);
|
||
|
||
const weeks: CalendarDay[][] = [];
|
||
for (let i = 0; i < calendarDays.length; i += 7) {
|
||
weeks.push(calendarDays.slice(i, i + 7));
|
||
}
|
||
|
||
return (
|
||
<div className="page--no-tab">
|
||
<PageHeader title="健康日历" />
|
||
<div className={styles.monthNav}>
|
||
<button onClick={() => setCurrentDate((d) => d.subtract(1, 'month'))}>‹</button>
|
||
<span className={styles.monthTitle}>{currentDate.format('YYYY年 M月')}</span>
|
||
<button onClick={() => setCurrentDate((d) => d.add(1, 'month'))}>›</button>
|
||
</div>
|
||
|
||
<div className={styles.weekdays}>
|
||
{['日', '一', '二', '三', '四', '五', '六'].map((w) => (
|
||
<span key={w} className={styles.weekday}>{w}</span>
|
||
))}
|
||
</div>
|
||
|
||
{weeks.map((week, wi) => (
|
||
<div key={wi} className={styles.week}>
|
||
{week.map((day) => (
|
||
<div
|
||
key={day.date}
|
||
className={`${styles.day} ${!day.isCurrentMonth ? styles.outside : ''} ${day.isToday ? styles.today : ''}`}
|
||
>
|
||
<span className={styles.dayNum}>{day.day}</span>
|
||
<div className={styles.markers}>
|
||
{day.markers.slice(0, 3).map((m, i) => (
|
||
<span
|
||
key={i}
|
||
className={styles.dot}
|
||
style={{ background: m.color }}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
))}
|
||
|
||
<Card className={styles.legend}>
|
||
<div className={styles.legendTitle}>图例</div>
|
||
<div className={styles.legendItems}>
|
||
<span className={styles.legendItem}><span className={styles.dot} style={{ background: '#2563EB' }} /> 测量日</span>
|
||
<span className={styles.legendItem}><span className={styles.dot} style={{ background: '#10B981' }} /> 已服药</span>
|
||
<span className={styles.legendItem}><span className={styles.dot} style={{ background: '#EF4444' }} /> 漏服药</span>
|
||
<span className={styles.legendItem}><span className={styles.dot} style={{ background: '#F59E0B' }} /> 复查日</span>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
);
|
||
}
|