- App.tsx: full navigation (Auth stack + Main tabs with 5 screens) - Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen - HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors - MapScreen: interactive map with layer toggles (6 layers) - MarketplaceScreen: categories (6), products (5), search - ChatScreen: AI chat with quick prompts (4), bot responses - ProfileScreen: user info, stats, menu (9 items), logout - AlertsScreen: alert list with severity, acknowledge - SensorsScreen: sensor list with type filters (6 types), search - ZonesScreen: zone cards with stats - SettingsScreen: language picker (FR/EN/ES/DE), privacy, about - Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n - Hooks: useSensors, useAlerts, useNotifications, useLocation - Components: Card, Button, LoadingSpinner, ErrorBoundary, Header - Services: iotService, notificationService (with axios API client) - Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN) - Theme: colors.ts with full design system (Blue Ocean palette) - Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
85 lines
3.8 KiB
TypeScript
85 lines
3.8 KiB
TypeScript
// Smart App City — Zones Screen
|
|
import React from 'react';
|
|
import { View, Text, ScrollView, TouchableOpacity, StyleSheet } from 'react-native';
|
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
|
import { Colors, Typography, Spacing, BorderRadius, Shadows } from '../../../../src/theme/colors';
|
|
import { useIoTStore } from '../../../stores/iotStore';
|
|
|
|
type Props = { navigation: NativeStackNavigationProp<any> };
|
|
|
|
export default function ZonesScreen({ navigation }: Props) {
|
|
const zones = useIoTStore((s) => s.zones);
|
|
const sensors = useIoTStore((s) => s.sensors);
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<View style={styles.header}>
|
|
<TouchableOpacity onPress={() => navigation.goBack()}>
|
|
<Text style={styles.backText}>← Retour</Text>
|
|
</TouchableOpacity>
|
|
<Text style={styles.title}>Zones</Text>
|
|
<Text style={styles.count}>{zones.length}</Text>
|
|
</View>
|
|
|
|
<ScrollView style={styles.list} showsVerticalScrollIndicator={false}>
|
|
{zones.map((zone) => {
|
|
const zoneSensors = sensors.filter((s) => s.zoneId === zone.id);
|
|
return (
|
|
<TouchableOpacity key={zone.id} style={styles.zoneCard}>
|
|
<View style={[styles.zoneColorBar, { backgroundColor: zone.color }]} />
|
|
<View style={styles.zoneContent}>
|
|
<Text style={styles.zoneName}>{zone.name}</Text>
|
|
<Text style={styles.zoneDesc}>{zone.description}</Text>
|
|
<View style={styles.zoneStats}>
|
|
<View style={styles.zoneStat}>
|
|
<Text style={styles.zoneStatValue}>{zoneSensors.length}</Text>
|
|
<Text style={styles.zoneStatLabel}>Capteurs</Text>
|
|
</View>
|
|
<View style={styles.zoneStat}>
|
|
<Text style={[styles.zoneStatValue, { color: zone.alertCount > 0 ? Colors.danger : Colors.success }]}>
|
|
{zone.alertCount}
|
|
</Text>
|
|
<Text style={styles.zoneStatLabel}>Alertes</Text>
|
|
</View>
|
|
<View style={styles.zoneStat}>
|
|
<Text style={styles.zoneStatValue}>{(zone.radius / 1000).toFixed(1)}km</Text>
|
|
<Text style={styles.zoneStatLabel}>Rayon</Text>
|
|
</View>
|
|
</View>
|
|
</View>
|
|
</TouchableOpacity>
|
|
);
|
|
})}
|
|
<View style={{ height: 40 }} />
|
|
</ScrollView>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: { flex: 1, backgroundColor: Colors.neutral50 },
|
|
header: {
|
|
flexDirection: 'row', alignItems: 'center',
|
|
backgroundColor: Colors.primary[500],
|
|
paddingTop: 50, paddingBottom: Spacing.base,
|
|
paddingHorizontal: Spacing.base, gap: Spacing.base,
|
|
},
|
|
backText: { color: Colors.white, fontSize: Typography.sizes.base },
|
|
title: { flex: 1, fontSize: Typography.sizes.lg, fontWeight: Typography.weights.bold, color: Colors.white },
|
|
count: { color: 'rgba(255,255,255,0.8)', fontSize: Typography.sizes.base },
|
|
list: { flex: 1, padding: Spacing.base },
|
|
zoneCard: {
|
|
flexDirection: 'row', backgroundColor: Colors.white,
|
|
borderRadius: BorderRadius.lg, marginBottom: Spacing.sm,
|
|
...Shadows.sm, overflow: 'hidden',
|
|
},
|
|
zoneColorBar: { width: 4 },
|
|
zoneContent: { flex: 1, padding: Spacing.base },
|
|
zoneName: { fontSize: Typography.sizes.md, fontWeight: Typography.weights.bold, color: Colors.neutral900 },
|
|
zoneDesc: { fontSize: Typography.sizes.sm, color: Colors.neutral500, marginTop: 2, marginBottom: Spacing.sm },
|
|
zoneStats: { flexDirection: 'row', gap: Spacing.base },
|
|
zoneStat: { alignItems: 'center' },
|
|
zoneStatValue: { fontSize: Typography.sizes.lg, fontWeight: Typography.weights.bold, color: Colors.primary[500] },
|
|
zoneStatLabel: { fontSize: Typography.sizes.xs, color: Colors.neutral400, marginTop: 2 },
|
|
});
|