fix: prevent duplicate consultations in backend, health calendar shows medication dots
This commit is contained in:
@@ -28,6 +28,12 @@ public class ConsultationService(AppDbContext db)
|
|||||||
|
|
||||||
public async Task<Consultation> StartAsync(Guid patientId, Guid doctorId, string subject)
|
public async Task<Consultation> StartAsync(Guid patientId, Guid doctorId, string subject)
|
||||||
{
|
{
|
||||||
|
// Reuse existing active consultation between this patient and doctor
|
||||||
|
var existing = await db.Consultations
|
||||||
|
.FirstOrDefaultAsync(c => c.PatientId == patientId && c.DoctorId == doctorId && c.Status == "active");
|
||||||
|
if (existing != null)
|
||||||
|
return existing;
|
||||||
|
|
||||||
var consultation = new Consultation
|
var consultation = new Consultation
|
||||||
{
|
{
|
||||||
PatientId = patientId,
|
PatientId = patientId,
|
||||||
|
|||||||
@@ -1,60 +1,87 @@
|
|||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo, useEffect } from 'react';
|
||||||
import { PageHeader } from '@/components/layout/PageHeader';
|
import { PageHeader } from '@/components/layout/PageHeader';
|
||||||
import { Card } from '@/components/common/Card';
|
import { Card } from '@/components/common/Card';
|
||||||
import type { CalendarDay } from '@/types';
|
import type { CalendarDay } from '@/types';
|
||||||
|
import { api } from '@/services/api-client';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import styles from './HealthCalendarPage.module.css';
|
import styles from './HealthCalendarPage.module.css';
|
||||||
|
|
||||||
const MARKER_COLORS: Record<string, string> = {
|
interface MedRecord { medicationId: string; timeSlot: string; takenAt?: string | null; isTaken: boolean; }
|
||||||
medication_taken: '#10B981',
|
interface HealthRecord { id: string; type: string; recordedAt: string; }
|
||||||
medication_missed: '#EF4444',
|
interface Medication { id: string; drugName: string; timeSlots: string[]; status: string; startDate: string; endDate?: string | null; }
|
||||||
follow_up: '#F59E0B',
|
|
||||||
measurement: '#2563EB',
|
|
||||||
};
|
|
||||||
|
|
||||||
export function HealthCalendarPage() {
|
export function HealthCalendarPage() {
|
||||||
const [currentDate, setCurrentDate] = useState(dayjs());
|
const [currentDate, setCurrentDate] = useState(dayjs());
|
||||||
|
const [medRecords, setMedRecords] = useState<MedRecord[]>([]);
|
||||||
|
const [medications, setMedications] = useState<Medication[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Fetch all active medications and their records
|
||||||
|
api.get<Medication[]>('/api/medications').then((res) => {
|
||||||
|
const active = res.data.filter((m) => m.status === 'active');
|
||||||
|
setMedications(active);
|
||||||
|
// Fetch records for each active medication
|
||||||
|
Promise.all(active.map((m) =>
|
||||||
|
api.get<MedRecord[]>(`/api/medications/${m.id}/records`)
|
||||||
|
.then((r) => r.data)
|
||||||
|
.catch(() => [] as MedRecord[])
|
||||||
|
)).then((all) => setMedRecords(all.flat()));
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const calendarDays = useMemo(() => {
|
const calendarDays = useMemo(() => {
|
||||||
const startOfMonth = currentDate.startOf('month');
|
const startOfMonth = currentDate.startOf('month');
|
||||||
const endOfMonth = currentDate.endOf('month');
|
const endOfMonth = currentDate.endOf('month');
|
||||||
const startDay = startOfMonth.day();
|
const startDay = startOfMonth.day();
|
||||||
const days: CalendarDay[] = [];
|
const days: CalendarDay[] = [];
|
||||||
|
|
||||||
const today = dayjs().format('YYYY-MM-DD');
|
const today = dayjs().format('YYYY-MM-DD');
|
||||||
|
|
||||||
|
// Build a map: date -> { taken, missed }
|
||||||
|
const dateMap: Record<string, { taken: number; missed: number }> = {};
|
||||||
|
medRecords.forEach((r) => {
|
||||||
|
const d = r.takenAt?.split('T')[0];
|
||||||
|
if (!d) return;
|
||||||
|
if (!dateMap[d]) dateMap[d] = { taken: 0, missed: 0 };
|
||||||
|
if (r.isTaken) dateMap[d].taken++;
|
||||||
|
else dateMap[d].missed++;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pad previous month
|
||||||
for (let i = startDay - 1; i >= 0; i--) {
|
for (let i = startDay - 1; i >= 0; i--) {
|
||||||
const d = startOfMonth.subtract(i + 1, 'day');
|
const d = startOfMonth.subtract(i + 1, 'day');
|
||||||
days.push({
|
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: [] });
|
||||||
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')) {
|
for (let d = startOfMonth; d.isBefore(endOfMonth) || d.isSame(endOfMonth, 'day'); d = d.add(1, 'day')) {
|
||||||
const dateStr = d.format('YYYY-MM-DD');
|
const dateStr = d.format('YYYY-MM-DD');
|
||||||
const markers: CalendarDay['markers'] = [];
|
const markers: CalendarDay['markers'] = [];
|
||||||
|
const dm = dateMap[dateStr];
|
||||||
|
|
||||||
// Calendar markers would be populated from real API data
|
if (dm) {
|
||||||
|
if (dm.taken > 0) {
|
||||||
days.push({
|
markers.push({ type: 'medication_taken', color: '#10B981', count: dm.taken });
|
||||||
date: dateStr,
|
}
|
||||||
year: d.year(),
|
if (dm.missed > 0) {
|
||||||
month: d.month() + 1,
|
markers.push({ type: 'medication_missed', color: '#EF4444', count: dm.missed });
|
||||||
day: d.date(),
|
}
|
||||||
isCurrentMonth: true,
|
} else {
|
||||||
isToday: dateStr === today,
|
// Check if any medication should have been taken on this date
|
||||||
markers,
|
const dateInRange = medications.some((m) => {
|
||||||
|
if (m.status !== 'active') return false;
|
||||||
|
const sd = m.startDate;
|
||||||
|
const ed = m.endDate || '9999-12-31';
|
||||||
|
return dateStr >= sd && dateStr <= ed;
|
||||||
});
|
});
|
||||||
|
if (dateInRange && medications.length > 0) {
|
||||||
|
markers.push({ type: 'medication_missed', color: '#FFA500', count: 0 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
days.push({ date: dateStr, year: d.year(), month: d.month() + 1, day: d.date(), isCurrentMonth: true, isToday: dateStr === today, markers });
|
||||||
}
|
}
|
||||||
|
|
||||||
return days;
|
return days;
|
||||||
}, [currentDate]);
|
}, [currentDate, medRecords, medications]);
|
||||||
|
|
||||||
const weeks: CalendarDay[][] = [];
|
const weeks: CalendarDay[][] = [];
|
||||||
for (let i = 0; i < calendarDays.length; i += 7) {
|
for (let i = 0; i < calendarDays.length; i += 7) {
|
||||||
@@ -79,18 +106,11 @@ export function HealthCalendarPage() {
|
|||||||
{weeks.map((week, wi) => (
|
{weeks.map((week, wi) => (
|
||||||
<div key={wi} className={styles.week}>
|
<div key={wi} className={styles.week}>
|
||||||
{week.map((day) => (
|
{week.map((day) => (
|
||||||
<div
|
<div key={day.date} className={`${styles.day} ${!day.isCurrentMonth ? styles.outside : ''} ${day.isToday ? styles.today : ''}`}>
|
||||||
key={day.date}
|
|
||||||
className={`${styles.day} ${!day.isCurrentMonth ? styles.outside : ''} ${day.isToday ? styles.today : ''}`}
|
|
||||||
>
|
|
||||||
<span className={styles.dayNum}>{day.day}</span>
|
<span className={styles.dayNum}>{day.day}</span>
|
||||||
<div className={styles.markers}>
|
<div className={styles.markers}>
|
||||||
{day.markers.slice(0, 3).map((m, i) => (
|
{day.markers.slice(0, 3).map((m, i) => (
|
||||||
<span
|
<span key={i} className={styles.dot} style={{ background: m.color }} />
|
||||||
key={i}
|
|
||||||
className={styles.dot}
|
|
||||||
style={{ background: m.color }}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -101,10 +121,9 @@ export function HealthCalendarPage() {
|
|||||||
<Card className={styles.legend}>
|
<Card className={styles.legend}>
|
||||||
<div className={styles.legendTitle}>图例</div>
|
<div className={styles.legendTitle}>图例</div>
|
||||||
<div className={styles.legendItems}>
|
<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: '#10B981' }} /> 已服药</span>
|
||||||
<span className={styles.legendItem}><span className={styles.dot} style={{ background: '#EF4444' }} /> 漏服药</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>
|
<span className={styles.legendItem}><span className={styles.dot} style={{ background: '#FFA500' }} /> 未记录</span>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user