196 lines
8.1 KiB
TypeScript
196 lines
8.1 KiB
TypeScript
import { ScrollView, Text, View } from "@tarojs/components";
|
||
import Taro from "@tarojs/taro";
|
||
import type { CSSProperties } from "react";
|
||
import { useMemo, useState } from "react";
|
||
import { ReportChartCard } from "../../components/report/chart-card";
|
||
import { ReportChipGroup } from "../../components/report/chip-group";
|
||
import { ReportDaytimePrediction } from "../../components/report/daytime-prediction";
|
||
import { ReportDistributionCard } from "../../components/report/distribution-card";
|
||
import { ReportInsightList } from "../../components/report/insight-list";
|
||
import { ReportKvList } from "../../components/report/kv-list";
|
||
import { ReportMetricGrid } from "../../components/report/metric-grid";
|
||
import { ReportPageHeader } from "../../components/report/page-header";
|
||
import { ReportScoreOverview } from "../../components/report/score-overview";
|
||
import { ReportSummaryBlock } from "../../components/report/summary-block";
|
||
import { reportDimensions, reportOptions, reportRecords } from "./mock";
|
||
import { getSleepLevel, pickReportRecord } from "./report-utils";
|
||
import type { ReportDimension } from "./types";
|
||
import "./index.scss";
|
||
|
||
function getDefaultSelection(dimension: ReportDimension) {
|
||
const options = reportOptions[dimension];
|
||
|
||
return {
|
||
dateKey: options.dates[0].value,
|
||
roomKey: options.rooms[0].value,
|
||
deviceKey: options.devices[0].value
|
||
};
|
||
}
|
||
|
||
export default function ReportPage() {
|
||
const [dimension, setDimension] = useState<ReportDimension>("daily");
|
||
const initialSelection = getDefaultSelection("daily");
|
||
const [dateKey, setDateKey] = useState(initialSelection.dateKey);
|
||
const [roomKey, setRoomKey] = useState(initialSelection.roomKey);
|
||
const [deviceKey, setDeviceKey] = useState(initialSelection.deviceKey);
|
||
|
||
const currentOptions = reportOptions[dimension];
|
||
const currentRecord = useMemo(
|
||
() => pickReportRecord(reportRecords[dimension], dateKey, roomKey, deviceKey),
|
||
[dateKey, deviceKey, dimension, roomKey]
|
||
);
|
||
const sleepLevel = getSleepLevel(currentRecord.score);
|
||
const [selectedSleepTrend, setSelectedSleepTrend] = useState(currentRecord.sleepTrend[0].id);
|
||
const [selectedScoreTrend, setSelectedScoreTrend] = useState(currentRecord.scoreTrend[0].id);
|
||
const [selectedHeartRatePoint, setSelectedHeartRatePoint] = useState(currentRecord.heartRatePoints[0].id);
|
||
const [selectedBreathingPoint, setSelectedBreathingPoint] = useState(currentRecord.breathingPoints[0].id);
|
||
const [selectedEventPoint, setSelectedEventPoint] = useState(currentRecord.eventPoints[0].id);
|
||
|
||
const resetSelections = (nextDimension: ReportDimension, nextDate: string, nextRoom: string, nextDevice: string) => {
|
||
const record = pickReportRecord(reportRecords[nextDimension], nextDate, nextRoom, nextDevice);
|
||
setSelectedSleepTrend(record.sleepTrend[0].id);
|
||
setSelectedScoreTrend(record.scoreTrend[0].id);
|
||
setSelectedHeartRatePoint(record.heartRatePoints[0].id);
|
||
setSelectedBreathingPoint(record.breathingPoints[0].id);
|
||
setSelectedEventPoint(record.eventPoints[0].id);
|
||
};
|
||
|
||
const handleDimensionChange = (nextValue: string) => {
|
||
const nextDimension = nextValue as ReportDimension;
|
||
const nextSelection = getDefaultSelection(nextDimension);
|
||
|
||
setDimension(nextDimension);
|
||
setDateKey(nextSelection.dateKey);
|
||
setRoomKey(nextSelection.roomKey);
|
||
setDeviceKey(nextSelection.deviceKey);
|
||
resetSelections(nextDimension, nextSelection.dateKey, nextSelection.roomKey, nextSelection.deviceKey);
|
||
};
|
||
|
||
const handleDateChange = (nextDate: string) => {
|
||
setDateKey(nextDate);
|
||
resetSelections(dimension, nextDate, roomKey, deviceKey);
|
||
};
|
||
|
||
const handleRoomChange = (nextRoom: string) => {
|
||
setRoomKey(nextRoom);
|
||
resetSelections(dimension, dateKey, nextRoom, deviceKey);
|
||
};
|
||
|
||
const handleDeviceChange = (nextDevice: string) => {
|
||
setDeviceKey(nextDevice);
|
||
resetSelections(dimension, dateKey, roomKey, nextDevice);
|
||
};
|
||
|
||
const pageStyle = {
|
||
"--report-top-gap": `${(typeof Taro.getWindowInfo === "function" ? Taro.getWindowInfo().statusBarHeight : Taro.getSystemInfoSync().statusBarHeight) || 0}px`
|
||
} as CSSProperties;
|
||
|
||
return (
|
||
<ScrollView className="report-page" scrollY style={pageStyle}>
|
||
<View className="report-page__glow report-page__glow--one" />
|
||
<View className="report-page__glow report-page__glow--two" />
|
||
|
||
<View className="report-page__content">
|
||
<ReportPageHeader
|
||
title={currentRecord.babyName}
|
||
subtitle={currentRecord.dateLabel}
|
||
onBack={() => Taro.navigateBack({ delta: 1 })}
|
||
onShare={() =>
|
||
Taro.showToast({
|
||
title: "分享功能待接入",
|
||
icon: "none"
|
||
})
|
||
}
|
||
/>
|
||
|
||
<View className="report-toolbar">
|
||
<ReportChipGroup options={reportDimensions} value={dimension} onChange={handleDimensionChange} />
|
||
|
||
<View className="report-filters">
|
||
<View className="report-filter">
|
||
<Text className="report-filter__label">日期</Text>
|
||
<ReportChipGroup options={currentOptions.dates} value={dateKey} onChange={handleDateChange} compact />
|
||
</View>
|
||
<View className="report-filter">
|
||
<Text className="report-filter__label">房间</Text>
|
||
<ReportChipGroup options={currentOptions.rooms} value={roomKey} onChange={handleRoomChange} compact />
|
||
</View>
|
||
<View className="report-filter">
|
||
<Text className="report-filter__label">设备</Text>
|
||
<ReportChipGroup options={currentOptions.devices} value={deviceKey} onChange={handleDeviceChange} compact />
|
||
</View>
|
||
</View>
|
||
</View>
|
||
|
||
<ReportScoreOverview
|
||
score={currentRecord.score}
|
||
levelLabel={sleepLevel.label}
|
||
levelTone={sleepLevel.tone}
|
||
qualityStatus={currentRecord.qualityStatus}
|
||
totalSleep={currentRecord.totalSleep}
|
||
asleepAt={currentRecord.asleepAt}
|
||
wakeAt={currentRecord.wakeAt}
|
||
factors={currentRecord.scoreFactors}
|
||
/>
|
||
|
||
<ReportChartCard
|
||
title="睡眠质量趋势"
|
||
subtitle="整夜睡眠阶段与呼吸波动"
|
||
points={currentRecord.sleepTrend}
|
||
selectedId={selectedSleepTrend}
|
||
onSelect={setSelectedSleepTrend}
|
||
/>
|
||
|
||
<ReportMetricGrid metrics={currentRecord.metrics} />
|
||
|
||
<ReportInsightList title="AI 睡眠分析" items={currentRecord.aiSummary} />
|
||
<ReportInsightList title="改善建议" items={currentRecord.suggestions} />
|
||
|
||
<ReportChartCard
|
||
title="睡眠分数趋势"
|
||
subtitle="点击节点查看阶段评分"
|
||
points={currentRecord.scoreTrend}
|
||
selectedId={selectedScoreTrend}
|
||
onSelect={setSelectedScoreTrend}
|
||
/>
|
||
|
||
<ReportSummaryBlock block={currentRecord.anomalyStats} />
|
||
<ReportKvList title="心率变异性(HRV)" items={currentRecord.hrvMetrics} />
|
||
|
||
<ReportChartCard
|
||
title="心率散点图"
|
||
subtitle="横轴时间,纵轴心率值"
|
||
points={currentRecord.heartRatePoints}
|
||
selectedId={selectedHeartRatePoint}
|
||
onSelect={setSelectedHeartRatePoint}
|
||
mode="scatter"
|
||
/>
|
||
|
||
<ReportChartCard
|
||
title="呼吸波形图"
|
||
subtitle="呼吸波动与暂停标记"
|
||
points={currentRecord.breathingPoints}
|
||
selectedId={selectedBreathingPoint}
|
||
onSelect={setSelectedBreathingPoint}
|
||
/>
|
||
|
||
<ReportSummaryBlock block={currentRecord.oxygenSummary} />
|
||
<ReportSummaryBlock block={currentRecord.snoreSummary} />
|
||
|
||
<ReportChartCard
|
||
title="异常事件散点图"
|
||
subtitle="呼吸暂停、心率异常、哭闹、离床"
|
||
points={currentRecord.eventPoints}
|
||
selectedId={selectedEventPoint}
|
||
onSelect={setSelectedEventPoint}
|
||
mode="scatter"
|
||
/>
|
||
|
||
<ReportDistributionCard title="睡眠结构" items={currentRecord.sleepStructure} />
|
||
<ReportKvList title="自主神经分析" items={currentRecord.autonomicMetrics} />
|
||
<ReportDaytimePrediction items={currentRecord.daytimePrediction} />
|
||
</View>
|
||
</ScrollView>
|
||
);
|
||
}
|