kintex/mobile/app/(tabs)/field.tsx
zio 6c9891c7e2 feat: mobile harness + design v2.1 (84 screens) + PLANNING v3.1 + deliverables + Stitch screens
- Harness: kintex-mobile-dev agent + kintex-mobile-orchestrator skill (WISE mobile ref, Stitch-first design rule, dual app targets B2B/B2C)
- design.md v2.1: full 84-screen inventory (web 51 / admin 10 / public 8 / mobile 15) with Stitch prompts incl. ticketing (SCR-P7/P8, M14/M15)
- PLANNING v3.1: unified account + split signup tracks (2FA required for staff, light signup/guest for visitors), one codebase / two app targets
- Deliverables: dev plan (21s), user/operator/developer guides (17/14/15s), program spec (44s, 65 programs, 8 flowcharts), DA (DB design 14s + table spec xlsx 35 tables/299 cols)
- Benchmark: ticketing-app-benchmark.md (7 apps) -> IMPLEMENTATION_BACKLOG Phase F (14 items)
- Stitch: 23 generated screens saved (mobile 10, admin 6, web core 5, ticket 2)
- mobile/: Expo scaffold (SDK 51, expo-router, secure store JWT)
- frontend: SCR-13~17 QA fixes, icons.tsx, kintexEvents, V10 seed migration
- ci/: KINTEX CI logo assets

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 23:31:45 +09:00

100 lines
3.0 KiB
TypeScript

/*
* 현장 탭 — 역할별 현장 기능 허브.
* 장치업체 → 현장 체크리스트(SCR-M1), 홀매니저 → 현장 검수(SCR-M2).
*/
import { Ionicons } from '@expo/vector-icons';
import { router } from 'expo-router';
import React from 'react';
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
import { Banner } from '../../components/Banner';
import { useAuth } from '../../context/AuthContext';
import { colors, radius, spacing, type } from '../../theme';
export default function FieldScreen() {
const { activeWorkspace, user } = useAuth();
const role = activeWorkspace?.myRole;
const isHallManager = user?.hallManager || role === 'HALL_MANAGER';
const isContractor = role === 'CONTRACTOR';
return (
<ScrollView contentContainerStyle={styles.scroll}>
<Text style={styles.title}> </Text>
{!isContractor && !isHallManager ? (
<Banner tone="info">
·() () .
</Banner>
) : null}
{isContractor ? (
<Tile
icon="clipboard-outline"
title="현장 체크리스트"
desc="장치 공정 확인 · 사진 첨부 · 검수 요청"
onPress={() => router.push('/checklist')}
/>
) : null}
{isHallManager ? (
<Tile
icon="shield-checkmark-outline"
title="현장 검수"
desc="승인 도면 vs 현장 사진 · 적합/부적합 판정"
onPress={() => router.push('/inspection')}
/>
) : null}
</ScrollView>
);
}
function Tile({
icon,
title,
desc,
onPress,
}: {
icon: keyof typeof Ionicons.glyphMap;
title: string;
desc: string;
onPress: () => void;
}) {
return (
<Pressable style={styles.tile} onPress={onPress}>
<View style={styles.tileIcon}>
<Ionicons name={icon} size={24} color={colors.primary600} />
</View>
<View style={styles.tileBody}>
<Text style={styles.tileTitle}>{title}</Text>
<Text style={styles.tileDesc}>{desc}</Text>
</View>
<Ionicons name="chevron-forward" size={20} color={colors.neutral500} />
</Pressable>
);
}
const styles = StyleSheet.create({
scroll: { padding: spacing.md, gap: spacing.md },
title: { fontSize: type.h2.fontSize, fontWeight: '700', color: colors.neutral900 },
tile: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
backgroundColor: colors.white,
borderWidth: 1,
borderColor: colors.neutral200,
borderRadius: radius.md,
padding: spacing.md,
},
tileIcon: {
width: 44,
height: 44,
borderRadius: radius.sm,
backgroundColor: colors.primary050,
alignItems: 'center',
justifyContent: 'center',
},
tileBody: { flex: 1, gap: 2 },
tileTitle: { fontSize: type.h3.fontSize, fontWeight: '600', color: colors.neutral900 },
tileDesc: { fontSize: type.caption.fontSize, color: colors.neutral500 },
});