Files
smart-city-digital-twin-mar…/smart-app-city/design/02-home-dashboard.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

334 lines
14 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 — 02 Home Dashboard</title>
<link rel="stylesheet" href="shared.css">
<style>
.dashboard-header {
padding: 16px;
background: linear-gradient(135deg, var(--primary-500), 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(3, 1fr);
gap: 8px; padding: 0 16px;
margin-top: -20px; margin-bottom: 16px;
}
.metric-card {
background: var(--neutral-0);
border-radius: var(--radius-lg);
padding: 14px 10px;
text-align: center;
box-shadow: var(--shadow-md);
border: 1px solid var(--neutral-100);
}
.metric-card-value {
font-size: var(--text-xl);
font-weight: var(--weight-bold);
}
.metric-card-label {
font-size: 10px;
color: var(--neutral-500);
margin-top: 2px;
}
.trend-up { color: var(--alert-success); }
.trend-down { color: var(--alert-danger); }
.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: 160px;
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: 12px; 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; }
/* Fixed viewport for PDF/iframe embedding */
html, body { width: 390px !important; height: 844px !important; overflow: hidden !important; margin: 0 !important; padding: 0 !important; }
</style>
<style>
/* 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 actifs</div>
</div>
<div class="metric-card">
<div class="metric-card-value" style="color:var(--ocean-500)">24°</div>
<div class="metric-card-label">Temp. moyenne</div>
</div>
<div class="metric-card">
<div class="metric-card-value" style="color:var(--alert-success)">98%</div>
<div class="metric-card-label">Uptime réseau</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 -->
<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 class="quick-actions" style="padding:0 16px 16px;">
<div class="quick-action">
<div class="quick-action-icon" style="background:linear-gradient(135deg,#2E7D32,#66BB6A);">🗺️</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,#7C4DFF,#B388FF);">🤖</div>
<span class="quick-action-label">AI Chat</span>
</div>
<div class="quick-action">
<div class="quick-action-icon" style="background:linear-gradient(135deg,#FF6D00,#FFAB40);">📸</div>
<span class="quick-action-label">Signaler</span>
</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:#E8F5E9;color:#2E7D32;">💨</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:#FCE4EC;color:#C62828;">💧</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:160px;background:linear-gradient(135deg,#E8F5E9,#E0F7FA);position:relative;overflow:hidden;display:flex;align-items:center;justify-content:center;">
<svg width="100%" height="100%" viewBox="0 0 320 160" style="position:absolute;top:0;left:0;">
<!-- Simplified Martinique outline -->
<path d="M80 130 Q100 90 140 75 Q180 55 220 60 Q260 65 280 90 Q300 115 290 135 Q270 150 230 148 Q190 150 150 140 Q110 145 80 130Z" fill="rgba(46,125,50,0.2)" stroke="rgba(46,125,50,0.4)" stroke-width="2"/>
<!-- Sensor dots -->
<circle cx="150" cy="95" r="6" fill="#2E7D32" opacity="0.8"><animate attributeName="r" values="6;10;6" dur="2s" repeatCount="indefinite"/></circle>
<circle cx="230" cy="105" r="6" fill="#00ACC1" opacity="0.8"><animate attributeName="r" values="6;10;6" dur="2.5s" repeatCount="indefinite"/></circle>
<circle cx="190" cy="120" r="5" fill="#FF6D00" opacity="0.8"><animate attributeName="r" values="5;9;5" dur="1.8s" repeatCount="indefinite"/></circle>
<circle cx="120" cy="115" r="5" fill="#7C4DFF" opacity="0.8"><animate attributeName="r" values="5;9;5" dur="2.2s" repeatCount="indefinite"/></circle>
<!-- Map labels -->
<text x="148" y="85" font-size="8" fill="#1B5E20" text-anchor="middle">FDF</text>
<text x="228" y="128" 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 class="page-label" style="position:absolute;bottom:88px;left:0;right:0;text-align:center;font-size:10px;color:var(--neutral-400);">02 — Home Dashboard</div>
</div>
</body>
</html>