feat(history): HistoryScreen with TrackRow + EmptyState
This commit is contained in:
1 parent
cd278aa2b1
commit
139294a6bd
1 file changed
+38
-75
@@ -16,6 +16,8 @@ import {
|
|||||||
type ContinuousUsageBlock,
|
type ContinuousUsageBlock,
|
||||||
} from '../training/continuousUsage';
|
} from '../training/continuousUsage';
|
||||||
import { TrainingChart } from '../components/TrainingChart';
|
import { TrainingChart } from '../components/TrainingChart';
|
||||||
|
import { TrackRow } from '../components/TrackRow';
|
||||||
|
import { EmptyState } from '../components/EmptyState';
|
||||||
import { colors, spacing, typography } from '../theme';
|
import { colors, spacing, typography } from '../theme';
|
||||||
|
|
||||||
type HistoryMode = 'windows' | 'continuous' | 'range';
|
type HistoryMode = 'windows' | 'continuous' | 'range';
|
||||||
@@ -360,46 +362,25 @@ function WindowHistory({
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.timeline}>
|
<View style={styles.timeline}>
|
||||||
{sessions.map(session => {
|
{sessions.map((session, index) => {
|
||||||
const isOpen = selectedId === session.id;
|
const isOpen = selectedId === session.id;
|
||||||
return (
|
return (
|
||||||
<View key={session.id} style={styles.timelineNode}>
|
<View key={session.id} style={styles.timelineNode}>
|
||||||
<Pressable
|
<TrackRow
|
||||||
accessibilityRole="button"
|
accessibilityLabel={`Janela ${formatSessionDay(session.startedAt)} ${formatSessionHour(session.startedAt)}`}
|
||||||
onPress={() => onOpenSession(session)}
|
day={formatSessionDay(session.startedAt)}
|
||||||
style={({ pressed }) => [
|
hour={formatSessionHour(session.startedAt)}
|
||||||
styles.sessionStrip,
|
isOpen={isOpen}
|
||||||
pressed ? styles.pressed : null,
|
meta={`${formatDuration(session.durationMs)} · ${session.sampleCount} amostras`}
|
||||||
|
metrics={[
|
||||||
|
{ label: `MÉD ${session.averageBpm}` },
|
||||||
|
{ label: `PICO ${session.peakBpm}`, tone: 'hot' as const },
|
||||||
|
{ label: `MIN ${session.minBpm}` },
|
||||||
]}
|
]}
|
||||||
|
onPress={() => onOpenSession(session)}
|
||||||
|
title={session.deviceName || 'Cinta cardíaca BLE'}
|
||||||
|
trackNumber={index + 1}
|
||||||
>
|
>
|
||||||
<View style={styles.dateBlock}>
|
|
||||||
<Text style={styles.sessionDay}>
|
|
||||||
{formatSessionDay(session.startedAt)}
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.sessionHour}>
|
|
||||||
{formatSessionHour(session.startedAt)}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View style={styles.sessionBody}>
|
|
||||||
<Text style={styles.sessionTitle}>
|
|
||||||
{session.deviceName || 'Cinta cardíaca BLE'}
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.sessionMeta}>
|
|
||||||
{formatDuration(session.durationMs)} · {session.sampleCount}{' '}
|
|
||||||
amostras · toque para detalhes
|
|
||||||
</Text>
|
|
||||||
<View style={styles.metricLine}>
|
|
||||||
<Text style={styles.metricText}>
|
|
||||||
MÉD {session.averageBpm}
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.metricTextHot}>
|
|
||||||
PICO {session.peakBpm}
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.metricText}>MIN {session.minBpm}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
{isOpen ? <OpenIcon /> : <ClosedIcon />}
|
|
||||||
</Pressable>
|
|
||||||
|
|
||||||
{isOpen ? (
|
{isOpen ? (
|
||||||
<WindowDetail
|
<WindowDetail
|
||||||
@@ -409,6 +390,7 @@ function WindowHistory({
|
|||||||
zones={zones}
|
zones={zones}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
</TrackRow>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -465,50 +447,30 @@ function ContinuousHistory({
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View style={styles.timeline}>
|
<View style={styles.timeline}>
|
||||||
{blocks.map(block => {
|
{blocks.map((block, index) => {
|
||||||
const isOpen = selectedUsageId === block.id;
|
const isOpen = selectedUsageId === block.id;
|
||||||
return (
|
return (
|
||||||
<View key={block.id} style={styles.timelineNode}>
|
<View key={block.id} style={styles.timelineNode}>
|
||||||
<Pressable
|
<TrackRow
|
||||||
accessibilityRole="button"
|
accessibilityLabel={`Uso contínuo ${formatSessionDay(block.startedAt)}`}
|
||||||
onPress={() => onOpenBlock(block)}
|
day={formatSessionDay(block.startedAt)}
|
||||||
style={({ pressed }) => [
|
hour={formatSessionHour(block.startedAt)}
|
||||||
styles.sessionStrip,
|
isOpen={isOpen}
|
||||||
pressed ? styles.pressed : null,
|
meta={`${formatUsageRange(block)} · ${block.sampleCount} amostras`}
|
||||||
|
metrics={[
|
||||||
|
{ label: `MÉD ${block.averageBpm}` },
|
||||||
|
{ label: `PICO ${block.peakBpm}`, tone: 'hot' as const },
|
||||||
|
{ label: `MIN ${block.minBpm}` },
|
||||||
]}
|
]}
|
||||||
|
onPress={() => onOpenBlock(block)}
|
||||||
|
title={block.deviceName || 'Uso contínuo'}
|
||||||
|
trackNumber={index + 1}
|
||||||
>
|
>
|
||||||
<View style={styles.dateBlock}>
|
|
||||||
<Text style={styles.sessionDay}>
|
|
||||||
{formatSessionDay(block.startedAt)}
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.sessionHour}>
|
|
||||||
{formatSessionHour(block.startedAt)}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View style={styles.sessionBody}>
|
|
||||||
<Text style={styles.sessionTitle}>
|
|
||||||
{block.deviceName || 'Uso contínuo'}
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.sessionMeta}>
|
|
||||||
{formatUsageRange(block)} · {block.sampleCount} amostras ·
|
|
||||||
toque para detalhes
|
|
||||||
</Text>
|
|
||||||
<View style={styles.metricLine}>
|
|
||||||
<Text style={styles.metricText}>
|
|
||||||
MÉD {block.averageBpm}
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.metricTextHot}>
|
|
||||||
PICO {block.peakBpm}
|
|
||||||
</Text>
|
|
||||||
<Text style={styles.metricText}>MIN {block.minBpm}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
{isOpen ? <OpenIcon /> : <ClosedIcon />}
|
|
||||||
</Pressable>
|
|
||||||
|
|
||||||
{isOpen && selectedBlock ? (
|
{isOpen && selectedBlock ? (
|
||||||
<UsageDetail block={selectedBlock} />
|
<UsageDetail block={selectedBlock} />
|
||||||
) : null}
|
) : null}
|
||||||
|
</TrackRow>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -591,11 +553,12 @@ function EmptyHistory({
|
|||||||
readonly title: string;
|
readonly title: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<View style={styles.emptyBlock}>
|
<EmptyState
|
||||||
<Text style={styles.eyebrow}>{eyebrow}</Text>
|
copy={copy}
|
||||||
<Text style={styles.emptyTitle}>{title}</Text>
|
eyebrow={eyebrow}
|
||||||
<Text style={styles.copy}>{copy}</Text>
|
title={title}
|
||||||
</View>
|
tone={eyebrow === 'ANÁLISE BLOQUEADA' ? 'red' : 'amber'}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user