DS 컴포넌트 9개 (ScreenPanel, WaveBars, AppStatusBar, Header, FilterChip 신규), 5개 탭 + 로그인 화면 D3RO 인스트루먼트 미학 적용, i18n 연결 (I18nProvider + 모바일 전용 키 80+개), 모노레포 workspaces에 apps/mobile 추가.
197 lines
6.7 KiB
TypeScript
197 lines
6.7 KiB
TypeScript
// apps/mobile/app/(tabs)/dash.tsx
|
|
// Dashboard tab — session stats, usage, system status
|
|
// Design ref: docs/v3/designs/dashboard.html
|
|
|
|
import { View, ScrollView, StyleSheet } from 'react-native'
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
import {
|
|
MetalCard,
|
|
PhosphorText,
|
|
Led,
|
|
ScreenPanel,
|
|
AppStatusBar,
|
|
Header,
|
|
d3roNativePalette
|
|
} from '@d3ro/ui-native'
|
|
import { useI18n } from '@d3ro/i18n'
|
|
|
|
export default function DashScreen(): React.ReactElement {
|
|
const insets = useSafeAreaInsets()
|
|
const { t } = useI18n()
|
|
|
|
return (
|
|
<ScrollView
|
|
style={styles.container}
|
|
contentContainerStyle={[styles.content, { paddingBottom: insets.bottom + 100 }]}
|
|
>
|
|
<Header title={t('mobile.dash.title')} showBorder={false} paddingTop={insets.top} />
|
|
|
|
{/* Session Overview — InsetPanel */}
|
|
<ScreenPanel style={styles.overviewPanel}>
|
|
<PhosphorText variant="small" color="muted" style={styles.sectionLabel}>
|
|
{t('mobile.dash.sessionOverview')}
|
|
</PhosphorText>
|
|
<View style={styles.bigNumber}>
|
|
<PhosphorText variant="hero" color="amber">0</PhosphorText>
|
|
<PhosphorText variant="body" color="muted" style={styles.todayLabel}>
|
|
{t('mobile.dash.today')}
|
|
</PhosphorText>
|
|
</View>
|
|
<PhosphorText variant="small" color="muted" style={styles.guideText}>
|
|
{t('mobile.dash.tapToRecord')}
|
|
</PhosphorText>
|
|
<View style={styles.overviewFooter}>
|
|
<View>
|
|
<PhosphorText variant="label" color="muted">{t('mobile.dash.words')}</PhosphorText>
|
|
<PhosphorText variant="value" color="amber">0</PhosphorText>
|
|
</View>
|
|
<View style={styles.alignEnd}>
|
|
<PhosphorText variant="label" color="muted">{t('mobile.dash.streak')}</PhosphorText>
|
|
<PhosphorText variant="value" color="amber">0</PhosphorText>
|
|
</View>
|
|
</View>
|
|
</ScreenPanel>
|
|
|
|
{/* 2x2 Stats Grid */}
|
|
<View style={styles.statsGrid}>
|
|
<MetalCard style={styles.statCard}>
|
|
<PhosphorText variant="label" color="muted">{t('mobile.dash.rec')}</PhosphorText>
|
|
<PhosphorText variant="value" color="amber">0</PhosphorText>
|
|
<PhosphorText variant="label" color="muted">{t('mobile.dash.min')}</PhosphorText>
|
|
</MetalCard>
|
|
<MetalCard style={styles.statCard}>
|
|
<PhosphorText variant="label" color="muted">{t('mobile.dash.words')}</PhosphorText>
|
|
<PhosphorText variant="value" color="amber">0</PhosphorText>
|
|
</MetalCard>
|
|
<MetalCard style={styles.statCard}>
|
|
<PhosphorText variant="label" color="muted">{t('mobile.dash.today')}</PhosphorText>
|
|
<PhosphorText variant="value" color="amber">0</PhosphorText>
|
|
</MetalCard>
|
|
<MetalCard style={styles.statCard}>
|
|
<PhosphorText variant="label" color="muted">{t('mobile.dash.streak')}</PhosphorText>
|
|
<PhosphorText variant="value" color="amber">0</PhosphorText>
|
|
</MetalCard>
|
|
</View>
|
|
|
|
{/* Backend Info */}
|
|
<MetalCard style={styles.infoCard}>
|
|
<PhosphorText variant="small" color="muted">{t('mobile.dash.backend')}</PhosphorText>
|
|
<View style={styles.infoRow}>
|
|
<Led color="green" size={6} />
|
|
<PhosphorText variant="body" color="amber" style={styles.infoValue}>
|
|
CLOUD (CLAUDE)
|
|
</PhosphorText>
|
|
</View>
|
|
</MetalCard>
|
|
|
|
<MetalCard style={styles.infoCard}>
|
|
<PhosphorText variant="small" color="muted">{t('mobile.dash.tier')}</PhosphorText>
|
|
<View style={styles.infoRow}>
|
|
<Led color="amber" size={6} />
|
|
<PhosphorText variant="body" color="amber" style={styles.infoValue}>
|
|
{t('mobile.dash.free')}
|
|
</PhosphorText>
|
|
</View>
|
|
</MetalCard>
|
|
|
|
{/* Usage */}
|
|
<PhosphorText variant="label" color="muted" style={styles.usageTitle}>
|
|
{t('mobile.dash.usage')}
|
|
</PhosphorText>
|
|
<MetalCard style={styles.usageCard}>
|
|
<UsageRow label={t('mobile.dash.dictation')} value={t('mobile.dash.unlimited')} />
|
|
<View style={styles.usageDivider} />
|
|
<UsageRow label={t('mobile.dash.llmProcess')} value={t('mobile.dash.unlimited')} />
|
|
<View style={styles.usageDivider} />
|
|
<UsageRow label={t('mobile.dash.premiumQuota')} value="250 / 500" progress={0.5} />
|
|
</MetalCard>
|
|
|
|
<AppStatusBar />
|
|
</ScrollView>
|
|
)
|
|
}
|
|
|
|
function UsageRow({
|
|
label,
|
|
value,
|
|
progress
|
|
}: {
|
|
label: string
|
|
value: string
|
|
progress?: number
|
|
}): React.ReactElement {
|
|
return (
|
|
<View style={styles.usageRow}>
|
|
<PhosphorText variant="body" color="primary">{label}</PhosphorText>
|
|
<View style={styles.usageRight}>
|
|
<PhosphorText variant="small" color="amber">{value}</PhosphorText>
|
|
{progress != null && (
|
|
<View style={styles.progressBar}>
|
|
<View style={[styles.progressFill, { width: `${progress * 100}%` }]} />
|
|
</View>
|
|
)}
|
|
</View>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: { flex: 1, backgroundColor: d3roNativePalette.bg.app },
|
|
content: { paddingHorizontal: 20 },
|
|
overviewPanel: { marginBottom: 12 },
|
|
sectionLabel: { marginBottom: 16 },
|
|
bigNumber: { flexDirection: 'row', alignItems: 'baseline', marginBottom: 8 },
|
|
todayLabel: { marginLeft: 12 },
|
|
guideText: { marginBottom: 20 },
|
|
overviewFooter: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
borderTopWidth: 1,
|
|
borderTopColor: d3roNativePalette.border.subtle,
|
|
paddingTop: 12
|
|
},
|
|
alignEnd: { alignItems: 'flex-end' },
|
|
statsGrid: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
gap: 12,
|
|
marginBottom: 12
|
|
},
|
|
statCard: {
|
|
width: '47%' as unknown as number,
|
|
alignItems: 'center',
|
|
paddingVertical: 16,
|
|
gap: 4
|
|
},
|
|
infoCard: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginBottom: 12,
|
|
paddingVertical: 14
|
|
},
|
|
infoRow: { flexDirection: 'row', alignItems: 'center' },
|
|
infoValue: { marginLeft: 8 },
|
|
usageTitle: { letterSpacing: 3, marginBottom: 8, marginLeft: 4 },
|
|
usageCard: { marginBottom: 12, padding: 0, overflow: 'hidden' },
|
|
usageRow: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
padding: 16
|
|
},
|
|
usageRight: { alignItems: 'flex-end', gap: 4 },
|
|
usageDivider: { height: 1, backgroundColor: d3roNativePalette.border.subtle },
|
|
progressBar: {
|
|
width: 80,
|
|
height: 4,
|
|
backgroundColor: d3roNativePalette.bg.inset,
|
|
borderRadius: 2,
|
|
overflow: 'hidden'
|
|
},
|
|
progressFill: {
|
|
height: '100%' as unknown as number,
|
|
backgroundColor: d3roNativePalette.accent.green,
|
|
borderRadius: 2
|
|
}
|
|
})
|