feat(smart-app): complete all remaining components, screens, hooks, services, stores, i18n
- i18n/index.ts: i18next setup with FR/EN/ES/DE translations - constants.ts: app config, sensor types, alert severity, storage keys, refresh intervals - store/index.ts: barrel export for all stores - iotStore.ts: full IoT store (6 sensors, 3 zones, 2 alerts) with actions - notificationStore.ts: notification store (5 mock notifications) with actions - uiStore.ts: theme/language store + translation maps for 4 languages - useSensors.ts: sensor filtering by type/zone, alert sensors selector - useAlerts.ts: active alerts, critical alerts, acknowledge - useNotifications.ts: notification CRUD operations - useLocation.ts: GPS location with expo-location, default Fort-de-France - SensorCard.tsx: full sensor card with status dot, compact mode - StatsCard.tsx: stats card with icon, value, trend - AlertCard.tsx: alert card with severity bar, acknowledge button - ZoneCard.tsx: zone card with color bar, sensor/alert counts - LineChart.tsx: bar-based line chart with Y-axis labels - BarChart.tsx: bar chart with value labels - GaugeChart.tsx: semi-circular gauge with color thresholds - MapView.tsx: map placeholder with overlay markers - MarkerPopup.tsx: popup with title, value, status, detail button - DashboardScreen.tsx: analytics dashboard with gauges + charts - SensorDetailScreen.tsx: sensor detail with gauge + history chart - NotificationPrefsScreen.tsx: notification preference toggles (4) - LayerDetailScreen.tsx: layer detail placeholder - iot.service.ts: CRUD operations for sensors, zones, alerts - gis.service.ts: geocoding, POI search, routing
This commit is contained in:
@@ -1,12 +1,73 @@
|
||||
// Smart App City — Dashboard Screen (detailed view with charts)
|
||||
import React from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
import { View, Text, ScrollView, StyleSheet } from 'react-native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { Colors, Typography, Spacing, BorderRadius } from '../../../../src/theme/colors';
|
||||
import { useIoTStore } from '../../../stores/iotStore';
|
||||
import LineChart from '../../charts/LineChart';
|
||||
import BarChart from '../../charts/BarChart';
|
||||
import GaugeChart from '../../charts/GaugeChart';
|
||||
import SectionHeader from '../../common/Header';
|
||||
|
||||
type Props = { navigation: NativeStackNavigationProp<any> };
|
||||
|
||||
const TEMP_DATA = [
|
||||
{ label: '6h', value: 22 }, { label: '8h', value: 23 }, { label: '10h', value: 25 },
|
||||
{ label: '12h', value: 27 }, { label: '14h', value: 29 }, { label: '16h', value: 28 },
|
||||
{ label: '18h', value: 26 }, { label: '20h', value: 24 },
|
||||
];
|
||||
|
||||
export default function DashboardScreen({ navigation }: Props) {
|
||||
const sensors = useIoTStore((s) => s.sensors);
|
||||
|
||||
const DashboardScreen = () => {
|
||||
return (
|
||||
<View>
|
||||
<Text>DashboardScreen</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
<ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>Dashboard Analytics</Text>
|
||||
</View>
|
||||
|
||||
export default DashboardScreen;
|
||||
{/* Gauges */}
|
||||
<View style={styles.section}>
|
||||
<SectionHeader title="Vue d'ensemble" />
|
||||
<View style={styles.gaugesRow}>
|
||||
<GaugeChart value={28.3} max={50} label="Température" unit="°C" />
|
||||
<GaugeChart value={72} max={100} label="Humidité" unit="%" />
|
||||
<GaugeChart value={85} max={200} label="Qualité Air" unit="AQI" color={Colors.warning} />
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* Line chart */}
|
||||
<View style={styles.section}>
|
||||
<SectionHeader title="Température — 24h" />
|
||||
<LineChart data={TEMP_DATA} color={Colors.ocean[500]} height={160} />
|
||||
</View>
|
||||
|
||||
{/* Bar chart */}
|
||||
<View style={styles.section}>
|
||||
<SectionHeader title="Énergie par zone (kWh)" />
|
||||
<BarChart
|
||||
data={[
|
||||
{ label: 'Centre', value: 2340, color: Colors.primary[500] },
|
||||
{ label: 'Schoelcher', value: 1890, color: Colors.ocean[500] },
|
||||
{ label: 'Nord', value: 3120, color: Colors.indigo[500] },
|
||||
]}
|
||||
height={140}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={{ height: 80 }} />
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: Colors.neutral50 },
|
||||
header: {
|
||||
backgroundColor: Colors.primary[500],
|
||||
paddingTop: 50, paddingBottom: Spacing.base,
|
||||
paddingHorizontal: Spacing.base,
|
||||
},
|
||||
title: { fontSize: Typography.sizes.lg, fontWeight: Typography.weights.bold, color: Colors.white },
|
||||
section: { padding: Spacing.base },
|
||||
gaugesRow: { flexDirection: 'row', justifyContent: 'space-around', marginTop: Spacing.base },
|
||||
});
|
||||
|
||||
@@ -1,12 +1,32 @@
|
||||
// Smart App City — Layer Detail Screen
|
||||
import React from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
import { View, Text, StyleSheet } from 'react-native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { Colors, Typography, Spacing } from '../../../../src/theme/colors';
|
||||
|
||||
const LayerDetailScreen = () => {
|
||||
type Props = { navigation: NativeStackNavigationProp<any> };
|
||||
|
||||
export default function LayerDetailScreen({ navigation }: Props) {
|
||||
return (
|
||||
<View>
|
||||
<Text>LayerDetailScreen</Text>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>Détail de la couche</Text>
|
||||
</View>
|
||||
<View style={styles.content}>
|
||||
<Text style={styles.hint}>Sélectionnez une couche sur la carte pour voir ses détails.</Text>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default LayerDetailScreen;
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: Colors.neutral50 },
|
||||
header: {
|
||||
backgroundColor: Colors.primary[500],
|
||||
paddingTop: 50, paddingBottom: Spacing.base,
|
||||
paddingHorizontal: Spacing.base,
|
||||
},
|
||||
title: { fontSize: Typography.sizes.lg, fontWeight: Typography.weights.bold, color: Colors.white },
|
||||
content: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: Spacing.xl },
|
||||
hint: { fontSize: Typography.sizes.base, color: Colors.neutral500, textAlign: 'center' },
|
||||
});
|
||||
|
||||
@@ -1,12 +1,88 @@
|
||||
// Smart App City — Sensor Detail Screen
|
||||
import React from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
import { View, Text, ScrollView, 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';
|
||||
import LineChart from '../../charts/LineChart';
|
||||
import GaugeChart from '../../charts/GaugeChart';
|
||||
|
||||
type Props = { navigation: NativeStackNavigationProp<any> };
|
||||
|
||||
export default function SensorDetailScreen({ navigation }: Props) {
|
||||
const sensors = useIoTStore((s) => s.sensors);
|
||||
const sensor = sensors[0]; // In real app, get by route param
|
||||
|
||||
if (!sensor) {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<Text style={styles.empty}>Capteur introuvable</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const SensorDetailScreen = () => {
|
||||
return (
|
||||
<View>
|
||||
<Text>SensorDetailScreen</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
<ScrollView style={styles.container} showsVerticalScrollIndicator={false}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>{sensor.name}</Text>
|
||||
<Text style={styles.subtitle}>{sensor.location}</Text>
|
||||
<View style={styles.valueRow}>
|
||||
<Text style={styles.value}>{sensor.value} {sensor.unit}</Text>
|
||||
<View style={[styles.statusBadge, { backgroundColor: sensor.status === 'ok' ? Colors.success : sensor.status === 'warning' ? Colors.warning : Colors.danger }]}>
|
||||
<Text style={styles.statusText}>{sensor.status.toUpperCase()}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
export default SensorDetailScreen;
|
||||
<View style={styles.section}>
|
||||
<GaugeChart value={sensor.value} max={100} label={sensor.type} unit={sensor.unit} size={140} />
|
||||
</View>
|
||||
|
||||
<View style={styles.section}>
|
||||
<LineChart
|
||||
data={[
|
||||
{ label: '6h', value: sensor.value - 3 }, { label: '8h', value: sensor.value - 2 },
|
||||
{ label: '10h', value: sensor.value - 1 }, { label: '12h', value: sensor.value },
|
||||
{ label: '14h', value: sensor.value + 1 }, { label: '16h', value: sensor.value },
|
||||
]}
|
||||
title="Historique 24h"
|
||||
height={160}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View style={styles.section}>
|
||||
<View style={styles.infoCard}>
|
||||
<Text style={styles.infoLabel}>Type</Text>
|
||||
<Text style={styles.infoValue}>{sensor.type}</Text>
|
||||
<Text style={styles.infoLabel}>Dernière mise à jour</Text>
|
||||
<Text style={styles.infoValue}>{new Date(sensor.lastUpdate).toLocaleString('fr-FR')}</Text>
|
||||
<Text style={styles.infoLabel}>Coordonnées</Text>
|
||||
<Text style={styles.infoValue}>{sensor.latitude.toFixed(4)}, {sensor.longitude.toFixed(4)}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: Colors.neutral50 },
|
||||
header: {
|
||||
backgroundColor: Colors.primary[500],
|
||||
paddingTop: 50, paddingBottom: Spacing.xl,
|
||||
paddingHorizontal: Spacing.base, alignItems: 'center',
|
||||
},
|
||||
title: { fontSize: Typography.sizes.lg, fontWeight: Typography.weights.bold, color: Colors.white, textAlign: 'center' },
|
||||
subtitle: { fontSize: Typography.sizes.sm, color: 'rgba(255,255,255,0.8)', marginTop: Spacing.xs },
|
||||
valueRow: { flexDirection: 'row', alignItems: 'center', gap: Spacing.base, marginTop: Spacing.base },
|
||||
value: { fontSize: Typography.sizes.xxxl, fontWeight: Typography.weights.bold, color: Colors.white },
|
||||
statusBadge: { borderRadius: BorderRadius.full, paddingHorizontal: Spacing.base, paddingVertical: 2 },
|
||||
statusText: { color: Colors.white, fontSize: Typography.sizes.xs, fontWeight: '700' },
|
||||
section: { padding: Spacing.base, alignItems: 'center' },
|
||||
empty: { fontSize: Typography.sizes.lg, color: Colors.neutral500, textAlign: 'center', marginTop: 100 },
|
||||
infoCard: {
|
||||
backgroundColor: Colors.white, borderRadius: BorderRadius.lg,
|
||||
padding: Spacing.base, width: '100%', ...Shadows.sm,
|
||||
},
|
||||
infoLabel: { fontSize: Typography.sizes.xs, color: Colors.neutral400, marginTop: Spacing.sm },
|
||||
infoValue: { fontSize: Typography.sizes.base, fontWeight: Typography.weights.semibold, color: Colors.neutral900 },
|
||||
});
|
||||
|
||||
@@ -1,12 +1,65 @@
|
||||
// Smart App City — Notification Preferences Screen
|
||||
import React from 'react';
|
||||
import { View, Text } from 'react-native';
|
||||
import { View, Text, ScrollView, Switch, StyleSheet } from 'react-native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
import { Colors, Typography, Spacing, BorderRadius, Shadows } from '../../../../src/theme/colors';
|
||||
import { useNotificationStore } from '../../../stores/notificationStore';
|
||||
|
||||
type Props = { navigation: NativeStackNavigationProp<any> };
|
||||
|
||||
export default function NotificationPrefsScreen({ navigation }: Props) {
|
||||
const store = useNotificationStore();
|
||||
|
||||
const rows = [
|
||||
{ key: 'pushEnabled' as const, label: 'Notifications push', desc: 'Recevoir des notifications push', icon: '🔔' },
|
||||
{ key: 'alertNotifications' as const, label: 'Alertes IoT', desc: 'Alertes capteurs et seuils', icon: '🚨' },
|
||||
{ key: 'eventNotifications' as const, label: 'Événements', desc: 'Événements de la ville', icon: '🎉' },
|
||||
{ key: 'systemNotifications' as const, label: 'Système', desc: 'Mises à jour et maintenance', icon: '⚙️' },
|
||||
];
|
||||
|
||||
const NotificationPrefsScreen = () => {
|
||||
return (
|
||||
<View>
|
||||
<Text>NotificationPrefsScreen</Text>
|
||||
<View style={styles.container}>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.title}>Préférences de notification</Text>
|
||||
</View>
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
{rows.map((row) => (
|
||||
<View key={row.key} style={styles.row}>
|
||||
<Text style={styles.icon}>{row.icon}</Text>
|
||||
<View style={styles.rowContent}>
|
||||
<Text style={styles.rowLabel}>{row.label}</Text>
|
||||
<Text style={styles.rowDesc}>{row.desc}</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={store[row.key]}
|
||||
onValueChange={() => store[`set${row.key.charAt(0).toUpperCase() + row.key.slice(1)}`](!store[row.key])}
|
||||
trackColor={{ false: Colors.neutral200, true: Colors.primary[300] }}
|
||||
thumbColor={store[row.key] ? Colors.primary[500] : Colors.neutral400}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default NotificationPrefsScreen;
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: Colors.neutral50 },
|
||||
header: {
|
||||
backgroundColor: Colors.primary[500],
|
||||
paddingTop: 50, paddingBottom: Spacing.base,
|
||||
paddingHorizontal: Spacing.base,
|
||||
},
|
||||
title: { fontSize: Typography.sizes.lg, fontWeight: Typography.weights.bold, color: Colors.white },
|
||||
row: {
|
||||
flexDirection: 'row', alignItems: 'center',
|
||||
backgroundColor: Colors.white,
|
||||
paddingHorizontal: Spacing.base, paddingVertical: Spacing.base,
|
||||
borderBottomWidth: 1, borderBottomColor: Colors.neutral100,
|
||||
gap: Spacing.base,
|
||||
},
|
||||
icon: { fontSize: 20 },
|
||||
rowContent: { flex: 1 },
|
||||
rowLabel: { fontSize: Typography.sizes.base, fontWeight: Typography.weights.semibold, color: Colors.neutral900 },
|
||||
rowDesc: { fontSize: Typography.sizes.sm, color: Colors.neutral500, marginTop: 2 },
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user