react-phone-apps/apps/property-survey/app/summary.tsx
lanrtop e7ab0d1ef1 feat: 汇总页新增聚合/分组视图与纯数据导出,实现地图侧栏聚合视图
- summary 新增明细/聚合/分组三 Tab,对应 SummaryAggregateList(楼层→分类)
  和 SummaryGroupList(按物品名分组,可展开查看分布位置)
- 新增 exportDataOnly() 仅导出 CSV 不含照片,header 新增两个导出按钮
- MarkerList 聚合 Tab 从 placeholder 升级为按颜色+分类分组的实际视图
- 蓝图新增 ai-recognize 模块(planned),引入 expo-image-manipulator /
  expo-secure-store 依赖为后续 AI 识别功能做准备

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 18:02:22 +09:00

122 lines
4.2 KiB
TypeScript

import { Alert, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
import { Ionicons } from '@expo/vector-icons'
import { useSummaryData } from '../src/features/summary/useSummaryData'
import { FilterBar } from '../src/features/summary/FilterBar'
import { SummaryStats } from '../src/features/summary/SummaryStats'
import { SummaryList } from '../src/features/summary/SummaryList'
import { SummaryAggregateList } from '../src/features/summary/SummaryAggregateList'
import { SummaryGroupList } from '../src/features/summary/SummaryGroupList'
import { exportWithPhotos, exportDataOnly } from '../src/features/summary/exportData'
import type { FloorNumber } from '@repo/types'
import { Stack, useFocusEffect } from 'expo-router'
import { useCallback, useState } from 'react'
type ViewMode = 'detail' | 'aggregate' | 'group'
export default function SummaryScreen() {
const { rows, filter, setFilter, refresh } = useSummaryData()
const [viewMode, setViewMode] = useState<ViewMode>('detail')
useFocusEffect(useCallback(() => {
refresh()
}, []))
async function handleExportData() {
try {
await exportDataOnly(rows)
} catch (err) {
Alert.alert('导出失败', String(err))
}
}
async function handleExportWithPhotos() {
try {
await exportWithPhotos(rows)
} catch (err) {
Alert.alert('导出失败', String(err))
}
}
return (
<>
<Stack.Screen
options={{
headerRight: () => (
<View style={styles.headerButtons}>
<TouchableOpacity onPress={handleExportData} style={styles.exportBtn}>
<Ionicons name="document-outline" size={22} color="#3B82F6" />
</TouchableOpacity>
<TouchableOpacity onPress={handleExportWithPhotos} style={styles.exportBtn}>
<Ionicons name="share-outline" size={22} color="#10B981" />
</TouchableOpacity>
</View>
),
}}
/>
<View style={styles.container}>
<SummaryStats rows={rows} />
<View style={styles.tabRow}>
<TouchableOpacity
style={[styles.tab, viewMode === 'detail' && styles.tabActive]}
onPress={() => setViewMode('detail')}
>
<Text style={[styles.tabText, viewMode === 'detail' && styles.tabTextActive]}></Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.tab, viewMode === 'aggregate' && styles.tabActive]}
onPress={() => setViewMode('aggregate')}
>
<Text style={[styles.tabText, viewMode === 'aggregate' && styles.tabTextActive]}></Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.tab, viewMode === 'group' && styles.tabActive]}
onPress={() => setViewMode('group')}
>
<Text style={[styles.tabText, viewMode === 'group' && styles.tabTextActive]}></Text>
</TouchableOpacity>
</View>
<FilterBar
floor={filter.floor}
keyword={filter.keyword}
onFloorChange={(floor) =>
setFilter((f) => ({ ...f, floor: floor as FloorNumber | 0 }))
}
onKeywordChange={(keyword) => setFilter((f) => ({ ...f, keyword }))}
/>
{viewMode === 'detail' && <SummaryList rows={rows} />}
{viewMode === 'aggregate' && <SummaryAggregateList rows={rows} />}
{viewMode === 'group' && <SummaryGroupList rows={rows} />}
</View>
</>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#fff' },
headerButtons: { flexDirection: 'row', alignItems: 'center' },
exportBtn: { paddingHorizontal: 8 },
tabRow: {
flexDirection: 'row',
paddingHorizontal: 12,
paddingVertical: 8,
gap: 8,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#E5E7EB',
backgroundColor: '#fff',
},
tab: {
paddingHorizontal: 18,
paddingVertical: 6,
borderRadius: 16,
borderWidth: 1,
borderColor: '#D1D5DB',
backgroundColor: '#F9FAFB',
},
tabActive: {
borderColor: '#10B981',
backgroundColor: '#ECFDF5',
},
tabText: { fontSize: 13, color: '#6B7280' },
tabTextActive: { color: '#065F46', fontWeight: '700' },
})