Files
smart-city-digital-twin-mar…/smart-app-city/design/02-home-dashboard-v2.html
Eric FELIXINE e30ae8ed09 feat(smart-app): implement complete mobile app MVP
- 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
2026-06-01 18:00:35 -04:00

390 lines
17 KiB
HTML

<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Smart App City v2 — 02 Home Dashboard</title>
<link rel="stylesheet" href="shared-blue.css">
<style>
.dashboard-header {
padding: 16px;
background: linear-gradient(135deg, var(--primary-600), var(--ocean-500));
color: white;
}
.dashboard-header-top {
display: flex; justify-content: space-between;
align-items: center; margin-bottom: 16px;
}
.greeting { font-size: var(--text-sm); opacity: 0.85; }
.user-name { font-size: var(--text-lg); font-weight: var(--weight-bold); }
.search-bar {
display: flex; align-items: center; gap: 8px;
background: rgba(255,255,255,0.2);
border-radius: var(--radius-full);
padding: 10px 16px;
backdrop-filter: blur(4px);
}
.search-bar input {
background: none; border: none; outline: none;
color: white; font-size: var(--text-sm); width: 100%;
}
.search-bar input::placeholder { color: rgba(255,255,255,0.6); }
.metrics-row {
display: grid; grid-template-columns: repeat(4, 1fr);
gap: 8px; padding: 0 16px;
margin-top: -20px; margin-bottom: 16px;
}
.metric-card {
background: var(--neutral-0);
border-radius: var(--radius-lg);
padding: 12px 8px;
text-align: center;
box-shadow: var(--shadow-md);
border: 1px solid var(--neutral-100);
}
.metric-card-value {
font-size: var(--text-lg);
font-weight: var(--weight-bold);
}
.metric-card-label {
font-size: 9px;
color: var(--neutral-500);
margin-top: 2px;
}
.section-title-row {
display: flex; justify-content: space-between;
align-items: center; padding: 0 16px 12px;
}
.section-title { font-size: var(--text-md); font-weight: var(--weight-bold); }
.see-all { font-size: var(--text-sm); color: var(--primary-500); font-weight: var(--weight-medium); }
.sensor-card {
flex-shrink: 0; width: 155px;
background: var(--neutral-0);
border-radius: var(--radius-lg);
padding: 14px;
box-shadow: var(--shadow-sm);
border: 1px solid var(--neutral-100);
}
.sensor-header {
display: flex; justify-content: space-between;
align-items: flex-start; margin-bottom: 10px;
}
.sensor-icon {
width: 36px; height: 36px;
border-radius: var(--radius-md);
display: flex; align-items: center; justify-content: center;
font-size: 18px;
}
.sensor-value { font-size: var(--text-xl); font-weight: var(--weight-bold); }
.sensor-unit { font-size: var(--text-xs); color: var(--neutral-500); }
.sensor-name { font-size: var(--text-xs); color: var(--neutral-600); margin-top: 4px; }
.sensor-location { font-size: 10px; color: var(--neutral-400); }
.quick-actions {
display: grid; grid-template-columns: repeat(4, 1fr);
gap: 10px; padding: 0 16px 16px;
}
.quick-action {
display: flex; flex-direction: column;
align-items: center; gap: 6px;
cursor: pointer;
}
.quick-action-icon {
width: 52px; height: 52px;
border-radius: var(--radius-lg);
display: flex; align-items: center; justify-content: center;
color: white; font-size: 22px;
}
.quick-action-label { font-size: 10px; color: var(--neutral-600); text-align: center; }
.alert-banner {
margin: 0 16px 12px;
background: rgba(211,47,47,0.08);
border: 1px solid rgba(211,47,47,0.2);
border-radius: var(--radius-lg);
padding: 12px 14px;
display: flex; gap: 10px; align-items: flex-start;
}
.alert-text { font-size: var(--text-sm); color: var(--alert-danger); font-weight: var(--weight-medium); }
.alert-sub { font-size: var(--text-xs); color: var(--neutral-600); }
.live-indicator {
display: flex; align-items: center; gap: 6px;
font-size: var(--text-xs);
color: var(--alert-success);
font-weight: var(--weight-medium);
}
.live-dot {
width: 8px; height: 8px;
background: var(--alert-success);
border-radius: 50%;
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
.section-padded { padding: 0 16px 12px; }
/* Service cards for new services */
.service-quick-card {
flex-shrink: 0;
width: 140px;
border-radius: var(--radius-lg);
padding: 14px;
color: white;
position: relative;
overflow: hidden;
}
.service-quick-card .svc-icon { font-size: 28px; margin-bottom: 8px; }
.service-quick-card .svc-name { font-size: var(--text-sm); font-weight: var(--weight-bold); }
.service-quick-card .svc-desc { font-size: 10px; opacity: 0.8; margin-top: 2px; }
.service-quick-card .svc-badge {
position: absolute; top: 10px; right: 10px;
background: rgba(255,255,255,0.2);
padding: 2px 8px;
border-radius: var(--radius-full);
font-size: 9px; font-weight: 600;
}
/* Fixed viewport for PDF/iframe embedding */
html, body { width: 390px !important; height: 844px !important; overflow: hidden !important; margin: 0 !important; padding: 0 !important; }
</style>
</head>
<body>
<div class="mobile-frame">
<!-- Status Bar -->
<div class="status-bar">
<span>9:41</span>
<div style="display:flex;gap:4px;align-items:center;">
<svg width="16" height="12" viewBox="0 0 16 12"><rect x="0" y="4" width="3" height="8" rx="1" fill="#333"/><rect x="4.5" y="2.5" width="3" height="9.5" rx="1" fill="#333"/><rect x="9" y="0" width="3" height="12" rx="1" fill="#333"/><rect x="13.5" y="0" width="2.5" height="12" rx="1" fill="#ccc"/></svg>
<svg width="20" height="10" viewBox="0 0 20 10"><rect x="0" y="0" width="17" height="10" rx="2" stroke="#333" stroke-width="1" fill="none"/><rect x="18" y="3" width="2" height="4" rx="1" fill="#333"/><rect x="1.5" y="1.5" width="10" height="7" rx="1" fill="#333"/></svg>
</div>
</div>
<!-- Header -->
<div class="dashboard-header">
<div class="dashboard-header-top">
<div>
<div class="greeting">Bonjour 👋</div>
<div class="user-name">Eric F.</div>
</div>
<div style="display:flex;gap:12px;align-items:center;">
<div class="live-indicator">
<div class="live-dot"></div>
EN DIRECT
</div>
<div style="position:relative;">
<svg class="icon-lg" viewBox="0 0 24 24"><path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 01-3.46 0"/></svg>
<div class="badge" style="position:absolute;top:-4px;right:-4px;">3</div>
</div>
</div>
</div>
<div class="search-bar">
<svg class="icon" viewBox="0 0 24 24" style="color:rgba(255,255,255,0.7)"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
<input type="text" placeholder="Rechercher un capteur, lieu, service...">
<svg class="icon" viewBox="0 0 24 24" style="color:rgba(255,255,255,0.5)"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="16" y2="12"/><line x1="4" y1="18" x2="12" y2="18"/></svg>
</div>
</div>
<!-- Content -->
<div class="content-area" style="background:var(--neutral-50);">
<!-- Metrics -->
<div class="metrics-row">
<div class="metric-card">
<div class="metric-card-value" style="color:var(--primary-500)">60</div>
<div class="metric-card-label">Capteurs</div>
</div>
<div class="metric-card">
<div class="metric-card-value" style="color:var(--ocean-500)">24°</div>
<div class="metric-card-label">Temp.</div>
</div>
<div class="metric-card">
<div class="metric-card-value" style="color:var(--alert-success)">98%</div>
<div class="metric-card-label">Uptime</div>
</div>
<div class="metric-card">
<div class="metric-card-value" style="color:var(--indigo-500)">42</div>
<div class="metric-card-label">AQI</div>
</div>
</div>
<!-- Alert Banner -->
<div class="alert-banner">
<svg class="icon" viewBox="0 0 24 24" style="color:var(--alert-danger);flex-shrink:0;"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
<div>
<div class="alert-text">Qualité air — Fort-de-France</div>
<div class="alert-sub">Indice élevé détecté · Secteur Centre-Ville · Il y a 12 min</div>
</div>
</div>
<!-- Quick Actions — expanded to 6 -->
<div class="section-padded" style="margin-top:8px;margin-bottom:12px;">
<div class="section-title" style="margin-bottom:12px;">Accès rapide</div>
</div>
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:10px;padding:0 16px 16px;">
<div class="quick-action">
<div class="quick-action-icon" style="background:linear-gradient(135deg,#1565C0,#42A5F5);">🗺️</div>
<span class="quick-action-label">Carte Live</span>
</div>
<div class="quick-action">
<div class="quick-action-icon" style="background:linear-gradient(135deg,#00ACC1,#4DD0E1);">🏪</div>
<span class="quick-action-label">Marché</span>
</div>
<div class="quick-action">
<div class="quick-action-icon" style="background:linear-gradient(135deg,#3949AB,#7986CB);">🤖</div>
<span class="quick-action-label">AI Chat</span>
</div>
<div class="quick-action">
<div class="quick-action-icon" style="background:linear-gradient(135deg,#F57C00,#FFAB40);">📸</div>
<span class="quick-action-label">Signaler</span>
</div>
<div class="quick-action">
<div class="quick-action-icon" style="background:linear-gradient(135deg,#00838F,#26C6DA);">🚌</div>
<span class="quick-action-label">Transport</span>
</div>
<div class="quick-action">
<div class="quick-action-icon" style="background:linear-gradient(135deg,#2E7D32,#66BB6A);"></div>
<span class="quick-action-label">Énergie</span>
</div>
</div>
<!-- New: Services rapides -->
<div class="section-title-row">
<div class="section-title">Services de la ville</div>
<div class="see-all">Tout voir →</div>
</div>
<div class="scroll-row" style="padding-bottom:16px;">
<div style="width:16px;flex-shrink:0;"></div>
<div class="service-quick-card" style="background:linear-gradient(135deg,#1565C0,#0D47A1);">
<div class="svc-badge">DPI</div>
<div class="svc-icon">💳</div>
<div class="svc-name">Wallet</div>
<div class="svc-desc">Paiements DPI</div>
</div>
<div class="service-quick-card" style="background:linear-gradient(135deg,#00838F,#006064);">
<div class="svc-badge">Temps réel</div>
<div class="svc-icon">🚌</div>
<div class="svc-name">Bus Ligne A1</div>
<div class="svc-desc">3 min · FDF</div>
</div>
<div class="service-quick-card" style="background:linear-gradient(135deg,#2E7D32,#1B5E20);">
<div class="svc-badge">IoT</div>
<div class="svc-icon"></div>
<div class="svc-name">Énergie</div>
<div class="svc-desc">Mon quartier</div>
</div>
<div class="service-quick-card" style="background:linear-gradient(135deg,#C62828,#B71C1C);">
<div class="svc-badge">Urgence</div>
<div class="svc-icon">🏥</div>
<div class="svc-name">Santé</div>
<div class="svc-desc">Pharmacies</div>
</div>
<div style="width:16px;flex-shrink:0;"></div>
</div>
<!-- Sensors scroll -->
<div class="section-title-row">
<div class="section-title">Capteurs en direct 📍</div>
<div class="see-all">Voir tout →</div>
</div>
<div class="scroll-row" style="padding-bottom:16px;">
<div style="width:16px;flex-shrink:0;"></div>
<div class="sensor-card">
<div class="sensor-header">
<div class="sensor-icon" style="background:#E3F2FD;color:#1565C0;">🌡️</div>
<div class="online-dot"></div>
</div>
<div class="sensor-value">24.3°</div>
<div class="sensor-unit">Température</div>
<div class="sensor-name">Temp-001</div>
<div class="sensor-location">Fort-de-France · Centre</div>
</div>
<div class="sensor-card">
<div class="sensor-header">
<div class="sensor-icon" style="background:#E0F7FA;color:#00838F;">💨</div>
<div class="online-dot"></div>
</div>
<div class="sensor-value">42</div>
<div class="sensor-unit">AQI (bon)</div>
<div class="sensor-name">AirQ-012</div>
<div class="sensor-location">Pointe des Nègres</div>
</div>
<div class="sensor-card">
<div class="sensor-header">
<div class="sensor-icon" style="background:#FFF3E0;color:#E65100;">💡</div>
<div class="online-dot" style="background:var(--alert-warning)"></div>
</div>
<div class="sensor-value">87%</div>
<div class="sensor-unit">Luminosité</div>
<div class="sensor-name">Lux-008</div>
<div class="sensor-location">Boulevard Général de Gaulle</div>
</div>
<div class="sensor-card">
<div class="sensor-header">
<div class="sensor-icon" style="background:#E8EAF6;color:#3949AB;">💧</div>
<div class="online-dot"></div>
</div>
<div class="sensor-value">65%</div>
<div class="sensor-unit">Humidité</div>
<div class="sensor-name">Hum-003</div>
<div class="sensor-location">Schœlcher · Campus</div>
</div>
<div style="width:16px;flex-shrink:0;"></div>
</div>
<!-- Map Mini Preview -->
<div class="section-title-row">
<div class="section-title">Aperçu carte</div>
<div class="see-all">Ouvrir →</div>
</div>
<div class="card" style="margin:0 16px 16px;height:150px;background:linear-gradient(135deg,#E3F2FD,#E0F7FA);position:relative;overflow:hidden;display:flex;align-items:center;justify-content:center;">
<svg width="100%" height="100%" viewBox="0 0 320 150" style="position:absolute;top:0;left:0;">
<path d="M80 120 Q100 80 140 65 Q180 45 220 50 Q260 55 280 80 Q300 105 290 125 Q270 140 230 138 Q190 140 150 130 Q110 135 80 120Z" fill="rgba(21,101,192,0.15)" stroke="rgba(21,101,192,0.3)" stroke-width="2"/>
<circle cx="150" cy="85" r="6" fill="#1565C0" opacity="0.8"><animate attributeName="r" values="6;10;6" dur="2s" repeatCount="indefinite"/></circle>
<circle cx="230" cy="95" r="6" fill="#00ACC1" opacity="0.8"><animate attributeName="r" values="6;10;6" dur="2.5s" repeatCount="indefinite"/></circle>
<circle cx="190" cy="110" r="5" fill="#3949AB" opacity="0.8"><animate attributeName="r" values="5;9;5" dur="1.8s" repeatCount="indefinite"/></circle>
<circle cx="120" cy="105" r="5" fill="#F57C00" opacity="0.8"><animate attributeName="r" values="5;9;5" dur="2.2s" repeatCount="indefinite"/></circle>
<text x="148" y="75" font-size="8" fill="#0D47A1" text-anchor="middle" font-weight="600">FDF</text>
<text x="228" y="118" font-size="7" fill="#666" text-anchor="middle">Schœlcher</text>
</svg>
<div style="position:absolute;bottom:10px;right:10px;background:white;border-radius:8px;padding:6px 12px;font-size:11px;font-weight:600;box-shadow:0 2px 8px rgba(0,0,0,0.1);">
🗺️ 4 zones actives
</div>
</div>
<div style="height:16px;"></div>
</div>
<!-- Bottom Nav -->
<div class="bottom-nav">
<div class="bottom-nav-item active">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22" fill="none" stroke="currentColor" stroke-width="2"/></svg>
<span>Accueil</span>
</div>
<div class="bottom-nav-item">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="1 6 1 22 8 18 16 22 23 18 23 2 16 6 8 2 1 6"/><line x1="8" y1="2" x2="8" y2="18"/><line x1="16" y1="6" x2="16" y2="22"/></svg>
<span>Carte</span>
</div>
<div class="bottom-nav-item">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>
<span>AI Chat</span>
</div>
<div class="bottom-nav-item">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
<span>Marché</span>
</div>
<div class="bottom-nav-item">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
<span>Profil</span>
</div>
</div>
<div style="position:absolute;bottom:88px;left:0;right:0;text-align:center;font-size:10px;color:var(--neutral-400);">02 — Home Dashboard v2 / Blue</div>
</div>
</body>
</html>