194 lines
6.8 KiB
TypeScript
194 lines
6.8 KiB
TypeScript
/*
|
|
* 홈/대시보드 — 활성 행사 헤더(D-데이) + 역할별 진입 카드.
|
|
* 워크스페이스(행사)는 로그인 응답에서 수신. 역할에 따라 진입 동선 분기.
|
|
*/
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import { router } from 'expo-router';
|
|
import React from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
import { Banner } from '../../components/Banner';
|
|
import { Card } from '../../components/Card';
|
|
import { DdayChip } from '../../components/DdayChip';
|
|
import { useAuth } from '../../context/AuthContext';
|
|
import type { EventRole, WorkspaceDto } from '../../lib/types';
|
|
import { colors, radius, spacing, type } from '../../theme';
|
|
|
|
export default function HomeScreen() {
|
|
const { t } = useTranslation();
|
|
const { user, workspaces, activeWorkspace, selectWorkspace } = useAuth();
|
|
|
|
return (
|
|
<ScrollView contentContainerStyle={styles.scroll}>
|
|
<Text style={styles.hello}>
|
|
{t('home.hello', { name: user?.displayName ?? t('common.user') })}
|
|
</Text>
|
|
|
|
{workspaces.length === 0 ? (
|
|
<Banner tone="info">{t('home.noEvent')}</Banner>
|
|
) : null}
|
|
|
|
{/* 행사 선택 */}
|
|
{workspaces.length > 1 ? (
|
|
<View style={styles.eventPicker}>
|
|
{workspaces.map((w) => (
|
|
<Pressable
|
|
key={w.eventId}
|
|
onPress={() => selectWorkspace(w.eventId)}
|
|
style={[
|
|
styles.eventChip,
|
|
activeWorkspace?.eventId === w.eventId && styles.eventChipOn,
|
|
]}
|
|
>
|
|
<Text
|
|
style={[
|
|
styles.eventChipText,
|
|
activeWorkspace?.eventId === w.eventId && styles.eventChipTextOn,
|
|
]}
|
|
numberOfLines={1}
|
|
>
|
|
{w.eventName}
|
|
</Text>
|
|
</Pressable>
|
|
))}
|
|
</View>
|
|
) : null}
|
|
|
|
{activeWorkspace ? <EventHeader ws={activeWorkspace} /> : null}
|
|
|
|
{/* 진행 스테퍼 */}
|
|
<Card>
|
|
<Text style={styles.cardTitle}>{t('home.progress')}</Text>
|
|
<Stepper
|
|
steps={[t('home.stepApply'), t('home.stepApprove'), t('home.stepBuild'), t('home.stepInspect')]}
|
|
active={1}
|
|
/>
|
|
</Card>
|
|
|
|
{/* 역할별 진입 */}
|
|
{activeWorkspace ? <RoleActions role={activeWorkspace.myRole} /> : null}
|
|
</ScrollView>
|
|
);
|
|
}
|
|
|
|
function EventHeader({ ws }: { ws: WorkspaceDto }) {
|
|
const { t } = useTranslation();
|
|
return (
|
|
<Card accent="none">
|
|
<View style={styles.eventHeaderTop}>
|
|
<Text style={styles.eventName}>{ws.eventName}</Text>
|
|
<DdayChip dday={ws.dday} />
|
|
</View>
|
|
<Text style={styles.eventMeta}>
|
|
{ws.hallLabel} · {ws.startDate} ~ {ws.endDate}
|
|
</Text>
|
|
<View style={styles.roleBadge}>
|
|
<Text style={styles.roleBadgeText}>{t(`roles.${ws.myRole}` as const)}</Text>
|
|
</View>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
function RoleActions({ role }: { role: EventRole }) {
|
|
const { t } = useTranslation();
|
|
const actions: { label: string; icon: keyof typeof Ionicons.glyphMap; onPress: () => void }[] = [];
|
|
|
|
if (role === 'CONTRACTOR') {
|
|
actions.push({ label: t('home.actionChecklist'), icon: 'clipboard-outline', onPress: () => router.push('/checklist') });
|
|
}
|
|
if (role === 'HALL_MANAGER') {
|
|
actions.push({ label: t('home.actionInspection'), icon: 'shield-checkmark-outline', onPress: () => router.push('/inspection') });
|
|
}
|
|
actions.push({ label: t('home.actionGallery'), icon: 'images-outline', onPress: () => router.push('/(tabs)/gallery') });
|
|
|
|
return (
|
|
<View style={styles.actions}>
|
|
{actions.map((a) => (
|
|
<Pressable key={a.label} style={styles.actionCard} onPress={a.onPress}>
|
|
<Ionicons name={a.icon} size={26} color={colors.primary600} />
|
|
<Text style={styles.actionLabel}>{a.label}</Text>
|
|
</Pressable>
|
|
))}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
function Stepper({ steps, active }: { steps: string[]; active: number }) {
|
|
return (
|
|
<View style={styles.stepper}>
|
|
{steps.map((s, i) => (
|
|
<View key={s} style={styles.step}>
|
|
<View
|
|
style={[
|
|
styles.stepDot,
|
|
i < active && { backgroundColor: colors.success },
|
|
i === active && { backgroundColor: colors.primary600 },
|
|
]}
|
|
>
|
|
<Text style={styles.stepDotText}>{i + 1}</Text>
|
|
</View>
|
|
<Text style={[styles.stepLabel, i === active && { color: colors.primary700, fontWeight: '700' }]}>
|
|
{s}
|
|
</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
scroll: { padding: spacing.md, gap: spacing.md },
|
|
hello: { fontSize: type.h2.fontSize, fontWeight: '700', color: colors.neutral900 },
|
|
eventPicker: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 },
|
|
eventChip: {
|
|
paddingHorizontal: 12,
|
|
paddingVertical: 8,
|
|
borderRadius: radius.pill,
|
|
borderWidth: 1,
|
|
borderColor: colors.neutral200,
|
|
maxWidth: 220,
|
|
},
|
|
eventChipOn: { backgroundColor: colors.primary100, borderColor: colors.primary600 },
|
|
eventChipText: { color: colors.neutral700, fontSize: type.caption.fontSize },
|
|
eventChipTextOn: { color: colors.primary700, fontWeight: '700' },
|
|
eventHeaderTop: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 8 },
|
|
eventName: { flex: 1, fontSize: type.h3.fontSize, fontWeight: '700', color: colors.neutral900 },
|
|
eventMeta: { marginTop: 4, color: colors.neutral500, fontSize: type.caption.fontSize },
|
|
roleBadge: {
|
|
alignSelf: 'flex-start',
|
|
marginTop: 10,
|
|
backgroundColor: colors.primary050,
|
|
borderRadius: radius.pill,
|
|
paddingHorizontal: 10,
|
|
paddingVertical: 3,
|
|
},
|
|
roleBadgeText: { color: colors.primary700, fontSize: type.caption.fontSize, fontWeight: '600' },
|
|
cardTitle: { fontSize: type.h3.fontSize, fontWeight: '600', color: colors.neutral900, marginBottom: 12 },
|
|
actions: { flexDirection: 'row', flexWrap: 'wrap', gap: spacing.md },
|
|
actionCard: {
|
|
flexGrow: 1,
|
|
flexBasis: '45%',
|
|
backgroundColor: colors.white,
|
|
borderWidth: 1,
|
|
borderColor: colors.neutral200,
|
|
borderRadius: radius.md,
|
|
padding: spacing.md,
|
|
gap: 8,
|
|
minHeight: 96,
|
|
justifyContent: 'center',
|
|
},
|
|
actionLabel: { fontSize: type.body.fontSize, fontWeight: '600', color: colors.neutral700 },
|
|
stepper: { flexDirection: 'row', justifyContent: 'space-between' },
|
|
step: { alignItems: 'center', flex: 1, gap: 6 },
|
|
stepDot: {
|
|
width: 28,
|
|
height: 28,
|
|
borderRadius: 14,
|
|
backgroundColor: colors.neutral200,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
stepDotText: { color: colors.white, fontWeight: '700', fontSize: type.caption.fontSize },
|
|
stepLabel: { fontSize: type.caption.fontSize, color: colors.neutral500 },
|
|
});
|