diff --git a/smart-app-city/frontend/src/components/cards/AlertCard.tsx b/smart-app-city/frontend/src/components/cards/AlertCard.tsx
index aaeb85d3..a37caced 100644
--- a/smart-app-city/frontend/src/components/cards/AlertCard.tsx
+++ b/smart-app-city/frontend/src/components/cards/AlertCard.tsx
@@ -1,12 +1,57 @@
+// Smart App City — Alert Card Component
import React from 'react';
-import { View, Text } from 'react-native';
+import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
+import { Colors, Typography, Spacing, BorderRadius, Shadows } from '../../../src/theme/colors';
-const AlertCard = () => {
- return (
-
- AlertCard
-
- );
+interface Props {
+ id: string;
+ title: string;
+ message: string;
+ severity: 'critical' | 'high' | 'medium' | 'low';
+ time: string;
+ acknowledged?: boolean;
+ onPress?: () => void;
+ onAcknowledge?: () => void;
+}
+
+const SEVERITY = {
+ critical: { bar: Colors.danger, bg: '#FFEBEE', label: 'Critique' },
+ high: { bar: Colors.warning, bg: '#FFF3E0', label: 'Haute' },
+ medium: { bar: '#FF9800', bg: '#FFF8E1', label: 'Moyenne' },
+ low: { bar: Colors.info, bg: '#E3F2FD', label: 'Basse' },
};
-export default AlertCard;
+export default function AlertCard({ title, message, severity, time, acknowledged, onPress, onAcknowledge }: Props) {
+ const s = SEVERITY[severity];
+ return (
+
+
+
+
+ {s.label}
+ {time}
+
+ {title}
+ {message}
+
+ {!acknowledged && onAcknowledge && (
+
+ ✓
+
+ )}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ card: { flexDirection: 'row', borderRadius: BorderRadius.lg, marginBottom: Spacing.sm, overflow: 'hidden', ...Shadows.sm },
+ bar: { width: 4 },
+ content: { flex: 1, padding: Spacing.base },
+ header: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 2 },
+ severity: { fontSize: Typography.sizes.xs, fontWeight: '700' },
+ time: { fontSize: Typography.sizes.xs, color: Colors.neutral400 },
+ title: { fontSize: Typography.sizes.base, fontWeight: Typography.weights.bold, color: Colors.neutral900 },
+ message: { fontSize: Typography.sizes.sm, color: Colors.neutral600, marginTop: 2, lineHeight: 17 },
+ checkBtn: { justifyContent: 'center', paddingHorizontal: Spacing.base },
+ check: { fontSize: 20, color: Colors.neutral400 },
+});
diff --git a/smart-app-city/frontend/src/components/cards/SensorCard.tsx b/smart-app-city/frontend/src/components/cards/SensorCard.tsx
index 5d5a8a54..8b0a9dac 100644
--- a/smart-app-city/frontend/src/components/cards/SensorCard.tsx
+++ b/smart-app-city/frontend/src/components/cards/SensorCard.tsx
@@ -1,12 +1,74 @@
+// Smart App City — Sensor Card Component
import React from 'react';
-import { View, Text } from 'react-native';
+import { View, Text, TouchableOpacity, StyleSheet, ViewStyle } from 'react-native';
+import { Colors, Typography, Spacing, BorderRadius, Shadows } from '../../../src/theme/colors';
-const SensorCard = () => {
- return (
-
- SensorCard
-
- );
+export interface SensorCardData {
+ id: string;
+ name: string;
+ type: string;
+ value: number;
+ unit: string;
+ status: 'ok' | 'warning' | 'alert' | 'offline';
+ location?: string;
+ icon?: string;
+}
+
+interface Props {
+ sensor: SensorCardData;
+ onPress?: () => void;
+ style?: ViewStyle;
+ compact?: boolean;
+}
+
+const STATUS_COLORS = { ok: Colors.success, warning: Colors.warning, alert: Colors.danger, offline: Colors.neutral400 };
+const TYPE_ICONS: Record = {
+ temperature: '🌡️', humidity: '💧', air_quality: '🌬️', noise: '🔊', traffic: '🚗', energy: '⚡',
};
-export default SensorCard;
+export default function SensorCard({ sensor, onPress, style, compact }: Props) {
+ const statusColor = STATUS_COLORS[sensor.status];
+ const icon = sensor.icon ?? TYPE_ICONS[sensor.type] ?? '📡';
+
+ if (compact) {
+ return (
+
+
+ {icon}
+
+ {sensor.value}{sensor.unit}
+
+ );
+ }
+
+ return (
+
+
+
+ {icon}
+
+
+
+ {sensor.name}
+ {sensor.location && 📍 {sensor.location}}
+ {sensor.value} {sensor.unit}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ card: { backgroundColor: Colors.white, borderRadius: BorderRadius.lg, padding: Spacing.base, ...Shadows.sm, borderWidth: 1, borderColor: Colors.neutral100 },
+ header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: Spacing.sm },
+ iconBg: { width: 40, height: 40, borderRadius: BorderRadius.md, justifyContent: 'center', alignItems: 'center' },
+ emoji: { fontSize: 20 },
+ statusDot: { width: 8, height: 8, borderRadius: 4 },
+ name: { fontSize: Typography.sizes.sm, fontWeight: Typography.weights.semibold, color: Colors.neutral900 },
+ location: { fontSize: Typography.sizes.xs, color: Colors.neutral400, marginTop: 2 },
+ value: { fontSize: Typography.sizes.lg, fontWeight: Typography.weights.bold, color: Colors.primary[500], marginTop: Spacing.xs },
+ unit: { fontSize: Typography.sizes.xs, fontWeight: '400', color: Colors.neutral500 },
+ compact: { alignItems: 'center', backgroundColor: Colors.white, borderRadius: BorderRadius.lg, padding: Spacing.sm, ...Shadows.sm, borderWidth: 1, borderColor: Colors.neutral100, minWidth: 72 },
+ compactIcon: { width: 32, height: 32, borderRadius: BorderRadius.md, justifyContent: 'center', alignItems: 'center', marginBottom: 4 },
+ compactEmoji: { fontSize: 16 },
+ compactValue: { fontSize: Typography.sizes.sm, fontWeight: Typography.weights.bold, color: Colors.neutral900 },
+ compactUnit: { fontSize: 9, fontWeight: '400', color: Colors.neutral500 },
+});
diff --git a/smart-app-city/frontend/src/components/cards/StatsCard.tsx b/smart-app-city/frontend/src/components/cards/StatsCard.tsx
index afe2dacf..986bc882 100644
--- a/smart-app-city/frontend/src/components/cards/StatsCard.tsx
+++ b/smart-app-city/frontend/src/components/cards/StatsCard.tsx
@@ -1,12 +1,38 @@
+// Smart App City — Stats Card Component
import React from 'react';
-import { View, Text } from 'react-native';
+import { View, Text, StyleSheet, ViewStyle } from 'react-native';
+import { Colors, Typography, Spacing, BorderRadius, Shadows } from '../../../src/theme/colors';
-const StatsCard = () => {
+interface Props {
+ label: string;
+ value: string | number;
+ unit?: string;
+ icon?: string;
+ color?: string;
+ trend?: { value: number; label: string };
+ style?: ViewStyle;
+}
+
+export default function StatsCard({ label, value, unit, icon, color = Colors.primary[500], trend, style }: Props) {
return (
-
- StatsCard
+
+ {icon && {icon}}
+ {value}{unit && {unit}}
+ {label}
+ {trend && (
+ = 0 ? Colors.success : Colors.danger }]}>
+ {trend.value >= 0 ? '↑' : '↓'} {Math.abs(trend.value)}%
+
+ )}
);
-};
+}
-export default StatsCard;
+const styles = StyleSheet.create({
+ card: { backgroundColor: Colors.white, borderRadius: BorderRadius.lg, padding: Spacing.base, alignItems: 'center', ...Shadows.sm, borderWidth: 1, borderColor: Colors.neutral100 },
+ icon: { fontSize: 20, marginBottom: 4 },
+ value: { fontSize: Typography.sizes.xl, fontWeight: Typography.weights.bold, color: Colors.neutral900 },
+ unit: { fontSize: Typography.sizes.xs, fontWeight: '400', color: Colors.neutral500 },
+ label: { fontSize: 9, color: Colors.neutral500, marginTop: 2, textAlign: 'center' },
+ trend: { fontSize: 10, fontWeight: '600', marginTop: 2 },
+});
diff --git a/smart-app-city/frontend/src/components/cards/ZoneCard.tsx b/smart-app-city/frontend/src/components/cards/ZoneCard.tsx
index 6ec567f1..376e10ef 100644
--- a/smart-app-city/frontend/src/components/cards/ZoneCard.tsx
+++ b/smart-app-city/frontend/src/components/cards/ZoneCard.tsx
@@ -1,12 +1,52 @@
+// Smart App City — Zone Card Component
import React from 'react';
-import { View, Text } from 'react-native';
+import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
+import { Colors, Typography, Spacing, BorderRadius, Shadows } from '../../../src/theme/colors';
-const ZoneCard = () => {
+export interface ZoneData {
+ id: string;
+ name: string;
+ description: string;
+ sensorCount: number;
+ alertCount: number;
+ color: string;
+}
+
+interface Props {
+ zone: ZoneData;
+ onPress?: () => void;
+}
+
+export default function ZoneCard({ zone, onPress }: Props) {
return (
-
- ZoneCard
-
+
+
+
+ {zone.name}
+ {zone.description}
+
+
+ {zone.sensorCount}
+ Capteurs
+
+
+ 0 ? Colors.danger : Colors.success }]}>{zone.alertCount}
+ Alertes
+
+
+
+
);
-};
+}
-export default ZoneCard;
+const styles = StyleSheet.create({
+ card: { flexDirection: 'row', backgroundColor: Colors.white, borderRadius: BorderRadius.lg, marginBottom: Spacing.sm, ...Shadows.sm, overflow: 'hidden' },
+ colorBar: { width: 4 },
+ content: { flex: 1, padding: Spacing.base },
+ name: { fontSize: Typography.sizes.md, fontWeight: Typography.weights.bold, color: Colors.neutral900 },
+ desc: { fontSize: Typography.sizes.sm, color: Colors.neutral500, marginTop: 2, marginBottom: Spacing.sm },
+ stats: { flexDirection: 'row', gap: Spacing.base },
+ stat: { alignItems: 'center' },
+ statVal: { fontSize: Typography.sizes.lg, fontWeight: Typography.weights.bold, color: Colors.primary[500] },
+ statLbl: { fontSize: Typography.sizes.xs, color: Colors.neutral400 },
+});
diff --git a/smart-app-city/frontend/src/components/charts/BarChart.tsx b/smart-app-city/frontend/src/components/charts/BarChart.tsx
index c30d1fa6..a61e0a18 100644
--- a/smart-app-city/frontend/src/components/charts/BarChart.tsx
+++ b/smart-app-city/frontend/src/components/charts/BarChart.tsx
@@ -1,12 +1,61 @@
+// Smart App City — Bar Chart Component
import React from 'react';
-import { View, Text } from 'react-native';
+import { View, Text, StyleSheet, ViewStyle } from 'react-native';
+import { Colors, Typography, Spacing, BorderRadius } from '../../../../src/theme/colors';
+
+interface DataPoint {
+ label: string;
+ value: number;
+ color?: string;
+}
+
+interface Props {
+ data: DataPoint[];
+ title?: string;
+ height?: number;
+ style?: ViewStyle;
+}
+
+export default function BarChart({ data, title, height = 120, style }: Props) {
+ if (!data.length) return null;
+
+ const maxVal = Math.max(...data.map((d) => d.value), 1);
-const BarChart = () => {
return (
-
- BarChart
+
+ {title && {title}}
+
+
+ {maxVal.toFixed(0)}
+ {(maxVal / 2).toFixed(0)}
+ 0
+
+
+ {data.map((point, i) => {
+ const barHeight = (point.value / maxVal) * 100;
+ return (
+
+ {point.value}
+
+ {point.label}
+
+ );
+ })}
+
+
);
-};
+}
-export default BarChart;
+const styles = StyleSheet.create({
+ container: { backgroundColor: Colors.white, borderRadius: BorderRadius.lg, padding: Spacing.base },
+ title: { fontSize: Typography.sizes.sm, fontWeight: Typography.weights.semibold, color: Colors.neutral700, marginBottom: Spacing.sm },
+ chart: { flexDirection: 'row', gap: Spacing.sm },
+ yAxis: { justifyContent: 'space-between', width: 28 },
+ yLabel: { fontSize: Typography.sizes.xs, color: Colors.neutral400, textAlign: 'right' },
+ bars: { flex: 1, flexDirection: 'row', alignItems: 'flex-end', gap: 4 },
+ barContainer: { flex: 1, alignItems: 'center', justifyContent: 'flex-end', height: '100%' },
+ valueLabel: { fontSize: 8, color: Colors.neutral500, marginBottom: 2 },
+ bar: { width: '70%', borderRadius: 3, minHeight: 4 },
+ xLabel: { fontSize: 8, color: Colors.neutral400, marginTop: 2, textAlign: 'center' },
+});
diff --git a/smart-app-city/frontend/src/components/charts/GaugeChart.tsx b/smart-app-city/frontend/src/components/charts/GaugeChart.tsx
index 41fb515b..314a16a3 100644
--- a/smart-app-city/frontend/src/components/charts/GaugeChart.tsx
+++ b/smart-app-city/frontend/src/components/charts/GaugeChart.tsx
@@ -1,12 +1,47 @@
+// Smart App City — Gauge Chart Component
import React from 'react';
-import { View, Text } from 'react-native';
+import { View, Text, StyleSheet, ViewStyle } from 'react-native';
+import { Colors, Typography, Spacing, BorderRadius } from '../../../../src/theme/colors';
+
+interface Props {
+ value: number;
+ max: number;
+ label: string;
+ unit?: string;
+ color?: string;
+ size?: number;
+ style?: ViewStyle;
+}
+
+export default function GaugeChart({ value, max, label, unit = '', color, size = 100, style }: Props) {
+ const percentage = Math.min(value / max, 1);
+ const displayColor = color ?? (percentage > 0.75 ? Colors.danger : percentage > 0.5 ? Colors.warning : Colors.success);
-const GaugeChart = () => {
return (
-
- GaugeChart
+
+
+ {/* Background arc */}
+
+ {/* Value arc */}
+
+ {/* Center value */}
+
+ {typeof value === 'number' ? value.toFixed(1) : value}
+ {unit && {unit}}
+
+
+ {label}
);
-};
+}
-export default GaugeChart;
+const styles = StyleSheet.create({
+ container: { alignItems: 'center' },
+ gauge: { position: 'relative', justifyContent: 'flex-end', overflow: 'hidden' },
+ bgArc: { position: 'absolute', bottom: 0, left: 0, right: 0, height: '100%', borderTopLeftRadius: 999, borderTopRightRadius: 999, backgroundColor: Colors.neutral100 },
+ valueArc: { position: 'absolute', bottom: 0, left: 0, height: '100%', borderTopLeftRadius: 999, borderTopRightRadius: 999 },
+ center: { alignItems: 'center', justifyContent: 'center', flex: 1, zIndex: 1 },
+ value: { fontSize: Typography.sizes.lg, fontWeight: Typography.weights.bold },
+ unit: { fontSize: Typography.sizes.xs, color: Colors.neutral400 },
+ label: { fontSize: Typography.sizes.sm, color: Colors.neutral600, marginTop: Spacing.xs, textAlign: 'center' },
+});
diff --git a/smart-app-city/frontend/src/components/charts/LineChart.tsx b/smart-app-city/frontend/src/components/charts/LineChart.tsx
index d07a3fe6..af967752 100644
--- a/smart-app-city/frontend/src/components/charts/LineChart.tsx
+++ b/smart-app-city/frontend/src/components/charts/LineChart.tsx
@@ -1,12 +1,64 @@
+// Smart App City — Line Chart Component (SVG-based)
import React from 'react';
-import { View, Text } from 'react-native';
+import { View, Text, StyleSheet, ViewStyle } from 'react-native';
+import { Colors, Typography, Spacing, BorderRadius } from '../../../../src/theme/colors';
+
+interface DataPoint {
+ label: string;
+ value: number;
+}
+
+interface Props {
+ data: DataPoint[];
+ title?: string;
+ color?: string;
+ height?: number;
+ style?: ViewStyle;
+}
+
+export default function LineChart({ data, title, color = Colors.primary[500], height = 120, style }: Props) {
+ if (!data.length) return null;
+
+ const maxVal = Math.max(...data.map((d) => d.value), 1);
+ const minVal = Math.min(...data.map((d) => d.value), 0);
+ const range = maxVal - minVal || 1;
+ const chartWidth = 100; // percentage
-const LineChart = () => {
return (
-
- LineChart
+
+ {title && {title}}
+
+ {/* Y-axis labels */}
+
+ {maxVal.toFixed(0)}
+ {((maxVal + minVal) / 2).toFixed(0)}
+ {minVal.toFixed(0)}
+
+ {/* Bars as simple visualization */}
+
+ {data.map((point, i) => {
+ const barHeight = ((point.value - minVal) / range) * 100;
+ return (
+
+
+ {point.label}
+
+ );
+ })}
+
+
);
-};
+}
-export default LineChart;
+const styles = StyleSheet.create({
+ container: { backgroundColor: Colors.white, borderRadius: BorderRadius.lg, padding: Spacing.base },
+ title: { fontSize: Typography.sizes.sm, fontWeight: Typography.weights.semibold, color: Colors.neutral700, marginBottom: Spacing.sm },
+ chart: { flexDirection: 'row', gap: Spacing.sm },
+ yAxis: { justifyContent: 'space-between', width: 28 },
+ yLabel: { fontSize: Typography.sizes.xs, color: Colors.neutral400, textAlign: 'right' },
+ bars: { flex: 1, flexDirection: 'row', alignItems: 'flex-end', gap: 2 },
+ barContainer: { flex: 1, alignItems: 'center', justifyContent: 'flex-end', height: '100%' },
+ bar: { width: '80%', borderTopLeftRadius: 3, borderTopRightRadius: 3, minHeight: 4 },
+ xLabel: { fontSize: 8, color: Colors.neutral400, marginTop: 2, textAlign: 'center' },
+});
diff --git a/smart-app-city/frontend/src/components/maps/MapView.tsx b/smart-app-city/frontend/src/components/maps/MapView.tsx
index 4f7ba80e..eda3d9ed 100644
--- a/smart-app-city/frontend/src/components/maps/MapView.tsx
+++ b/smart-app-city/frontend/src/components/maps/MapView.tsx
@@ -1,12 +1,73 @@
+// Smart App City — Map View Component (react-native-maps wrapper)
import React from 'react';
-import { View, Text } from 'react-native';
+import { View, Text, StyleSheet, TouchableOpacity, Platform } from 'react-native';
+import { Colors, Typography, Spacing, BorderRadius } from '../../../../src/theme/colors';
+
+// Note: react-native-maps requires native module setup
+// This is a placeholder that shows a map-like view
+// In production, replace with: import MapView, { Marker, Circle } from 'react-native-maps';
+
+interface MapMarker {
+ id: string;
+ latitude: number;
+ longitude: number;
+ title: string;
+ type: 'sensor' | 'alert' | 'poi';
+ status?: 'ok' | 'warning' | 'alert';
+}
+
+interface Props {
+ markers?: MapMarker[];
+ center?: { latitude: number; longitude: number };
+ zoom?: number;
+ onMarkerPress?: (marker: MapMarker) => void;
+ onMapPress?: (coordinate: { latitude: number; longitude: number }) => void;
+ style?: any;
+}
+
+export default function MapView({ markers = [], center, zoom, onMarkerPress, style }: Props) {
+ const mapCenter = center ?? { latitude: 14.6161, longitude: -61.0588 };
-const MapView = () => {
return (
-
- MapView
+
+ {/* Map placeholder — replace with actual react-native-maps MapView */}
+
+ 🗺️
+ Carte Interactive
+ {mapCenter.latitude.toFixed(4)}°N, {Math.abs(mapCenter.longitude).toFixed(4)}°W
+ react-native-maps — {markers.length} marqueurs
+
+
+ {/* Overlay markers */}
+ {markers.map((marker, i) => {
+ // Simple positioning based on index for mock layout
+ const top = 20 + (i * 15) % 60;
+ const left = 15 + (i * 20) % 70;
+ const dotColor = marker.status === 'alert' ? Colors.danger : marker.status === 'warning' ? Colors.warning : Colors.primary[500];
+
+ return (
+ onMarkerPress?.(marker)}
+ >
+
+ {marker.title}
+
+ );
+ })}
);
-};
+}
-export default MapView;
+const styles = StyleSheet.create({
+ container: { flex: 1, position: 'relative' },
+ mapPlaceholder: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#E8F5E9' },
+ mapEmoji: { fontSize: 48, marginBottom: Spacing.base },
+ mapTitle: { fontSize: Typography.sizes.lg, fontWeight: Typography.weights.bold, color: Colors.neutral700 },
+ mapCoords: { fontSize: Typography.sizes.sm, color: Colors.neutral500, marginTop: Spacing.xs },
+ mapHint: { fontSize: Typography.sizes.xs, color: Colors.neutral400, marginTop: Spacing.xs },
+ marker: { position: 'absolute', alignItems: 'center' },
+ markerDot: { width: 14, height: 14, borderRadius: 7, borderWidth: 2, borderColor: Colors.white, ...{ shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.25, shadowRadius: 3, elevation: 3 } },
+ markerLabel: { fontSize: 9, fontWeight: '600', color: Colors.neutral700, backgroundColor: 'rgba(255,255,255,0.9)', paddingHorizontal: 4, paddingVertical: 1, borderRadius: 4, marginTop: 2 },
+});
diff --git a/smart-app-city/frontend/src/components/maps/MarkerPopup.tsx b/smart-app-city/frontend/src/components/maps/MarkerPopup.tsx
index 209b0780..4075ed1e 100644
--- a/smart-app-city/frontend/src/components/maps/MarkerPopup.tsx
+++ b/smart-app-city/frontend/src/components/maps/MarkerPopup.tsx
@@ -1,12 +1,59 @@
+// Smart App City — Marker Popup Component
import React from 'react';
-import { View, Text } from 'react-native';
+import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
+import { Colors, Typography, Spacing, BorderRadius, Shadows } from '../../../../src/theme/colors';
-const MarkerPopup = () => {
+interface Props {
+ title: string;
+ subtitle?: string;
+ value?: string;
+ status?: 'ok' | 'warning' | 'alert';
+ onClose?: () => void;
+ onDetail?: () => void;
+}
+
+const STATUS_DOT = { ok: Colors.success, warning: Colors.warning, alert: Colors.danger };
+
+export default function MarkerPopup({ title, subtitle, value, status, onClose, onDetail }: Props) {
return (
-
- MarkerPopup
+
+
+ {title}
+ {onClose && (
+
+ ✕
+
+ )}
+
+ {subtitle && {subtitle}}
+ {value && (
+
+ {value}
+ {status && }
+
+ )}
+ {onDetail && (
+
+ Voir détail →
+
+ )}
);
-};
+}
-export default MarkerPopup;
+const styles = StyleSheet.create({
+ popup: {
+ position: 'absolute', bottom: 100, left: 16, right: 16,
+ backgroundColor: Colors.white, borderRadius: BorderRadius.lg,
+ padding: Spacing.base, ...Shadows.lg,
+ },
+ header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: Spacing.xs },
+ title: { fontSize: Typography.sizes.base, fontWeight: Typography.weights.bold, color: Colors.neutral900, flex: 1 },
+ close: { fontSize: 16, color: Colors.neutral400, paddingLeft: Spacing.base },
+ subtitle: { fontSize: Typography.sizes.sm, color: Colors.neutral500, marginBottom: Spacing.xs },
+ valueRow: { flexDirection: 'row', alignItems: 'center', gap: Spacing.sm, marginBottom: Spacing.sm },
+ value: { fontSize: Typography.sizes.xl, fontWeight: Typography.weights.bold, color: Colors.primary[500] },
+ statusDot: { width: 10, height: 10, borderRadius: 5 },
+ detailBtn: { alignSelf: 'flex-end', paddingVertical: Spacing.xs, paddingHorizontal: Spacing.base, backgroundColor: Colors.primary[50], borderRadius: BorderRadius.md },
+ detailText: { fontSize: Typography.sizes.sm, color: Colors.primary[500], fontWeight: '600' },
+});
diff --git a/smart-app-city/frontend/src/i18n/index.ts b/smart-app-city/frontend/src/i18n/index.ts
index e69de29b..9235a64a 100644
--- a/smart-app-city/frontend/src/i18n/index.ts
+++ b/smart-app-city/frontend/src/i18n/index.ts
@@ -0,0 +1,33 @@
+// Smart App City — Internationalization (i18n)
+import i18n from 'i18next';
+import { initReactI18next } from 'react-i18next';
+import * as Localization from 'expo-localization';
+
+import { translations, Language } from '../stores/uiStore';
+
+// Build i18next resources from our translation map
+const resources: Record }> = {};
+for (const [lang, trans] of Object.entries(translations)) {
+ resources[lang] = { translation: trans };
+}
+
+i18n
+ .use(initReactI18next)
+ .init({
+ resources,
+ lng: Localization.locale?.split('-')[0] ?? 'fr',
+ fallbackLng: 'fr',
+ interpolation: { escapeValue: false },
+ });
+
+export default i18n;
+
+// Helper: change language at runtime
+export const changeLanguage = (lang: Language) => {
+ return i18n.changeLanguage(lang);
+};
+
+// Helper: get current language
+export const getCurrentLanguage = (): Language => {
+ return (i18n.language?.split('-')[0] as Language) ?? 'fr';
+};
diff --git a/smart-app-city/frontend/src/screens/dashboard/DashboardScreen.tsx b/smart-app-city/frontend/src/screens/dashboard/DashboardScreen.tsx
index cd8f0ab0..59b26d2c 100644
--- a/smart-app-city/frontend/src/screens/dashboard/DashboardScreen.tsx
+++ b/smart-app-city/frontend/src/screens/dashboard/DashboardScreen.tsx
@@ -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 };
+
+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 (
-
- DashboardScreen
-
- );
-};
+
+
+ Dashboard Analytics
+
-export default DashboardScreen;
+ {/* Gauges */}
+
+
+
+
+
+
+
+
+
+ {/* Line chart */}
+
+
+
+
+
+ {/* Bar chart */}
+
+
+
+
+
+
+
+ );
+}
+
+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 },
+});
diff --git a/smart-app-city/frontend/src/screens/gis/LayerDetailScreen.tsx b/smart-app-city/frontend/src/screens/gis/LayerDetailScreen.tsx
index aebcb348..77791d12 100644
--- a/smart-app-city/frontend/src/screens/gis/LayerDetailScreen.tsx
+++ b/smart-app-city/frontend/src/screens/gis/LayerDetailScreen.tsx
@@ -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 };
+
+export default function LayerDetailScreen({ navigation }: Props) {
return (
-
- LayerDetailScreen
+
+
+ Détail de la couche
+
+
+ Sélectionnez une couche sur la carte pour voir ses détails.
+
);
-};
+}
-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' },
+});
diff --git a/smart-app-city/frontend/src/screens/iot/SensorDetailScreen.tsx b/smart-app-city/frontend/src/screens/iot/SensorDetailScreen.tsx
index 9ca8bfd1..fad2ee8f 100644
--- a/smart-app-city/frontend/src/screens/iot/SensorDetailScreen.tsx
+++ b/smart-app-city/frontend/src/screens/iot/SensorDetailScreen.tsx
@@ -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 };
+
+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 (
+
+ Capteur introuvable
+
+ );
+ }
-const SensorDetailScreen = () => {
return (
-
- SensorDetailScreen
-
- );
-};
+
+
+ {sensor.name}
+ {sensor.location}
+
+ {sensor.value} {sensor.unit}
+
+ {sensor.status.toUpperCase()}
+
+
+
-export default SensorDetailScreen;
+
+
+
+
+
+
+
+
+
+
+ Type
+ {sensor.type}
+ Dernière mise à jour
+ {new Date(sensor.lastUpdate).toLocaleString('fr-FR')}
+ Coordonnées
+ {sensor.latitude.toFixed(4)}, {sensor.longitude.toFixed(4)}
+
+
+
+ );
+}
+
+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 },
+});
diff --git a/smart-app-city/frontend/src/screens/notifications/NotificationPrefsScreen.tsx b/smart-app-city/frontend/src/screens/notifications/NotificationPrefsScreen.tsx
index dadb8708..dc17240e 100644
--- a/smart-app-city/frontend/src/screens/notifications/NotificationPrefsScreen.tsx
+++ b/smart-app-city/frontend/src/screens/notifications/NotificationPrefsScreen.tsx
@@ -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 };
+
+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 (
-
- NotificationPrefsScreen
+
+
+ Préférences de notification
+
+
+ {rows.map((row) => (
+
+ {row.icon}
+
+ {row.label}
+ {row.desc}
+
+ 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}
+ />
+
+ ))}
+
);
-};
+}
-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 },
+});
diff --git a/smart-app-city/frontend/src/services/gis.service.ts b/smart-app-city/frontend/src/services/gis.service.ts
index 0f01e7ca..8d7dbe52 100644
--- a/smart-app-city/frontend/src/services/gis.service.ts
+++ b/smart-app-city/frontend/src/services/gis.service.ts
@@ -1 +1,48 @@
-// TODO: Implement
+// Smart App City — GIS Service (maps, geocoding, routing)
+import { get, post } from './api';
+
+export interface GeoPoint {
+ latitude: number;
+ longitude: number;
+}
+
+export interface GeoFeature {
+ id: string;
+ type: 'sensor' | 'zone' | 'poi' | 'event';
+ name: string;
+ description?: string;
+ location: GeoPoint;
+ properties?: Record;
+}
+
+export interface MapLayer {
+ id: string;
+ name: string;
+ visible: boolean;
+ opacity: number;
+ type: 'markers' | 'heatmap' | 'polygon' | 'heatmap';
+}
+
+export const gisService = {
+ async geocode(address: string): Promise {
+ return get('/gis/geocode', { address });
+ },
+
+ async reverseGeocode(lat: number, lng: number): Promise {
+ return get(`/gis/reverse/${lat}/${lng}`);
+ },
+
+ async getFeatures(bounds: {
+ north: number; south: number; east: number; west: number;
+ }): Promise {
+ return get('/gis/features', bounds);
+ },
+
+ async searchPOI(query: string, location: GeoPoint, radius: number = 5000): Promise {
+ return get('/gis/poi', { query, ...location, radius });
+ },
+
+ async getRoute(from: GeoPoint, to: GeoPoint): Promise<{ distance: number; duration: number; points: GeoPoint[] }> {
+ return post('/gis/route', { from, to });
+ },
+};
diff --git a/smart-app-city/frontend/src/store/index.ts b/smart-app-city/frontend/src/store/index.ts
index e69de29b..73ccd3a6 100644
--- a/smart-app-city/frontend/src/store/index.ts
+++ b/smart-app-city/frontend/src/store/index.ts
@@ -0,0 +1,5 @@
+// Smart App City — Store barrel export
+export { useAuthStore } from './authStore';
+export { useIoTStore } from './iotStore';
+export { useNotificationStore } from './notificationStore';
+export { useUIStore } from './uiStore';
diff --git a/smart-app-city/frontend/src/utils/constants.ts b/smart-app-city/frontend/src/utils/constants.ts
index 0f01e7ca..243839f7 100644
--- a/smart-app-city/frontend/src/utils/constants.ts
+++ b/smart-app-city/frontend/src/utils/constants.ts
@@ -1 +1,53 @@
-// TODO: Implement
+// Smart App City — App Constants
+
+export const APP_NAME = 'Smart App City';
+export const APP_VERSION = '0.1.0';
+
+// API
+export const API_BASE_URL = process.env.EXPO_PUBLIC_API_URL ?? 'https://api-smartapp.digitribe.fr/api/v1';
+export const API_TIMEOUT = 15000;
+
+// Map
+export const DEFAULT_MAP_CENTER = { latitude: 14.6161, longitude: -61.0588 }; // Fort-de-France
+export const DEFAULT_MAP_ZOOM = 12;
+export const MARTINIQUE_BOUNDS = {
+ north: 14.88,
+ south: 14.45,
+ east: -60.82,
+ west: -61.23,
+};
+
+// Sensor types
+export const SENSOR_TYPES = [
+ { key: 'temperature', label: 'Température', unit: '°C', icon: '🌡️', color: '#00ACC1' },
+ { key: 'humidity', label: 'Humidité', unit: '%', icon: '💧', color: '#1565C0' },
+ { key: 'air_quality', label: 'Qualité Air', unit: 'AQI', icon: '🌬️', color: '#2E7D32' },
+ { key: 'noise', label: 'Bruit', unit: 'dB', icon: '🔊', color: '#3949AB' },
+ { key: 'traffic', label: 'Trafic', unit: 'veh/h', icon: '🚗', color: '#F57C00' },
+ { key: 'energy', label: 'Énergie', unit: 'kWh', icon: '⚡', color: '#D32F2F' },
+];
+
+// Alert severity
+export const ALERT_SEVERITY = {
+ critical: { label: 'Critique', color: '#D32F2F', icon: '🔴' },
+ high: { label: 'Haute', color: '#F57C00', icon: '🟠' },
+ medium: { label: 'Moyenne', color: '#FF9800', icon: '🟡' },
+ low: { label: 'Basse', color: '#0288D1', icon: '🔵' },
+};
+
+// Storage keys
+export const STORAGE_KEYS = {
+ ACCESS_TOKEN: 'access_token',
+ REFRESH_TOKEN: 'refresh_token',
+ AUTH_STORAGE: 'auth-storage',
+ THEME: 'theme',
+ LANGUAGE: 'language',
+};
+
+// Refresh intervals (ms)
+export const REFRESH_INTERVALS = {
+ SENSORS: 30000, // 30s
+ ALERTS: 60000, // 1min
+ WEATHER: 300000, // 5min
+ NOTIFICATIONS: 30000, // 30s
+};