- 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
327 lines
13 KiB
HTML
327 lines
13 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 — 05 AI Chat (Blue v2)</title>
|
|
<link rel="stylesheet" href="shared-blue.css">
|
|
<style>
|
|
.chat-header {
|
|
display: flex; align-items: center; gap: 12px;
|
|
background: linear-gradient(135deg, var(--primary-500), var(--ocean-500));
|
|
padding: 14px 16px; color: white;
|
|
}
|
|
.chat-avatar {
|
|
width: 42px; height: 42px; border-radius: var(--radius-full);
|
|
background: linear-gradient(135deg, rgba(255,255,255,0.3), rgba(255,255,255,0.15));
|
|
border: 2px solid rgba(255,255,255,0.4);
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 20px;
|
|
}
|
|
.chat-agent-name { font-size: var(--text-md); font-weight: 700; }
|
|
.chat-agent-status { font-size: 11px; opacity: 0.8; display: flex; align-items: center; gap: 4px; }
|
|
|
|
.chat-area {
|
|
padding: 16px 12px;
|
|
background: var(--neutral-50);
|
|
display: flex; flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.msg-row {
|
|
display: flex; gap: 8px; align-items: flex-end;
|
|
max-width: 88%;
|
|
}
|
|
.msg-row.bot { align-self: flex-start; }
|
|
.msg-row.user { align-self: flex-end; flex-direction: row-reverse; }
|
|
|
|
.msg-avatar {
|
|
width: 30px; height: 30px; border-radius: var(--radius-full);
|
|
background: linear-gradient(135deg, var(--primary-500), var(--ocean-500));
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 14px; flex-shrink: 0; color: white;
|
|
}
|
|
|
|
.msg-bubble {
|
|
padding: 10px 14px; border-radius: 18px;
|
|
font-size: var(--text-sm); line-height: 1.45;
|
|
}
|
|
.msg-row.bot .msg-bubble {
|
|
background: white; color: var(--neutral-900);
|
|
border: 1px solid var(--neutral-200);
|
|
border-bottom-left-radius: 6px;
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
.msg-row.user .msg-bubble {
|
|
background: var(--primary-500); color: white;
|
|
border-bottom-right-radius: 6px;
|
|
}
|
|
.msg-time { font-size: 10px; color: var(--neutral-400); padding: 2px 6px; }
|
|
|
|
.quick-replies {
|
|
display: flex; flex-wrap: wrap; gap: 6px;
|
|
padding: 0 12px; margin-top: 6px;
|
|
}
|
|
.qr-btn {
|
|
padding: 7px 14px; border-radius: var(--radius-full);
|
|
border: 1.5px solid var(--primary-500);
|
|
background: var(--primary-50); color: var(--primary-600);
|
|
font-size: var(--text-sm); font-weight: 500; cursor: pointer;
|
|
transition: all 0.2s; white-space: nowrap;
|
|
}
|
|
.qr-btn:hover { background: var(--primary-500); color: white; }
|
|
|
|
.inline-card {
|
|
background: white; border-radius: var(--radius-lg);
|
|
border: 1px solid var(--neutral-200); overflow: hidden;
|
|
box-shadow: var(--shadow-sm);
|
|
max-width: 320px;
|
|
}
|
|
.inline-card-header {
|
|
padding: 10px 14px;
|
|
display: flex; align-items: center; gap: 8px;
|
|
font-size: var(--text-sm); font-weight: 600;
|
|
}
|
|
.inline-card-body { padding: 0 14px 12px; }
|
|
.iot-row {
|
|
display: flex; justify-content: space-between;
|
|
align-items: center; padding: 6px 0;
|
|
border-bottom: 1px solid var(--neutral-100);
|
|
font-size: var(--text-sm);
|
|
}
|
|
.iot-row:last-child { border-bottom: none; }
|
|
.iot-value { font-weight: 700; }
|
|
.iot-value.green { color: var(--alert-success); }
|
|
.iot-value.orange { color: var(--alert-warning); }
|
|
.iot-value.blue { color: var(--primary-500); }
|
|
.iot-value.ocean { color: var(--ocean-500); }
|
|
|
|
.typing-indicator {
|
|
display: flex; align-items: center; gap: 4px;
|
|
padding: 14px 16px;
|
|
background: white; border-radius: 18px;
|
|
border: 1px solid var(--neutral-200);
|
|
border-bottom-left-radius: 6px;
|
|
width: fit-content; margin-left: 38px;
|
|
}
|
|
.typing-dot {
|
|
width: 8px; height: 8px;
|
|
border-radius: 50%; background: var(--neutral-400);
|
|
animation: typingBounce 1.4s infinite;
|
|
}
|
|
.typing-dot:nth-child(2) { animation-delay: 0.2s; }
|
|
.typing-dot:nth-child(3) { animation-delay: 0.4s; }
|
|
@keyframes typingBounce {
|
|
0%, 60%, 100% { transform: translateY(0); }
|
|
30% { transform: translateY(-5px); }
|
|
}
|
|
|
|
.chat-input-row {
|
|
display: flex; gap: 8px; align-items: center;
|
|
padding: 10px 12px; background: white;
|
|
border-top: 1px solid var(--neutral-200);
|
|
margin-top: auto;
|
|
}
|
|
.chat-input {
|
|
flex: 1; padding: 10px 16px;
|
|
border-radius: var(--radius-full);
|
|
border: 1.5px solid var(--neutral-300);
|
|
font-size: var(--text-sm); outline: none;
|
|
transition: border-color 0.2s;
|
|
}
|
|
.chat-input:focus { border-color: var(--primary-500); }
|
|
.send-btn {
|
|
width: 42px; height: 42px; border-radius: var(--radius-full);
|
|
background: var(--primary-500); color: white;
|
|
display: flex; align-items: center; justify-content: center;
|
|
border: none; cursor: pointer; flex-shrink: 0;
|
|
}
|
|
/* 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>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Chat Header -->
|
|
<div class="chat-header">
|
|
<div class="chat-avatar">🤖</div>
|
|
<div>
|
|
<div class="chat-agent-name">AI Assistant · Smart City</div>
|
|
<div class="chat-agent-status"><div class="live-dot"></div>En ligne</div>
|
|
</div>
|
|
<div style="margin-left:auto;display:flex;gap:12px;">
|
|
<svg class="icon-lg" viewBox="0 0 24 24"><path d="M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z"/><path d="M19 10v2a7 7 0 01-14 0v-2"/><line x1="12" y1="19" x2="12" y2="23"/><line x1="8" y1="23" x2="16" y2="23"/></svg>
|
|
<svg class="icon-lg" viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Chat Content -->
|
|
<div class="content-area" style="background:var(--neutral-50);display:flex;flex-direction:column;">
|
|
|
|
<div class="chat-area" style="flex:1;">
|
|
|
|
<!-- Bot message 1 -->
|
|
<div class="msg-row bot">
|
|
<div class="msg-avatar">🤖</div>
|
|
<div>
|
|
<div class="msg-bubble">Bonjour Eric ! 👋 Je suis votre assistant Smart City. Comment puis-je vous aider aujourd'hui à Fort-de-France ?</div>
|
|
<div class="msg-time">09:12</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- User message 1 -->
|
|
<div class="msg-row user">
|
|
<div>
|
|
<div class="msg-bubble">Quelle est la qualité de l'air en ce moment ?</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bot message 2 with inline IoT card -->
|
|
<div class="msg-row bot">
|
|
<div class="msg-avatar">🤖</div>
|
|
<div style="max-width:92%;">
|
|
<div class="msg-bubble" style="border-bottom-left-radius:6px;margin-bottom:8px;">Les données de nos capteurs IoT en temps réel :</div>
|
|
<div class="inline-card">
|
|
<div class="inline-card-header">
|
|
<div style="width:32px;height:32px;border-radius:10px;background:#E3F2FD;display:flex;align-items:center;justify-content:center;font-size:16px;">📍</div>
|
|
<div>Capteurs Air — Fort-de-France</div>
|
|
<div class="live-dot" style="margin-left:auto;"></div>
|
|
</div>
|
|
<div class="inline-card-body">
|
|
<div class="iot-row">
|
|
<span>🌫️ PM2.5</span>
|
|
<span class="iot-value green">12 µg/m³</span>
|
|
</div>
|
|
<div class="iot-row">
|
|
<span>💨 AQI Global</span>
|
|
<span class="iot-value green">42 (Bon)</span>
|
|
</div>
|
|
<div class="iot-row">
|
|
<span>🌡° Température</span>
|
|
<span class="iot-value blue">26.5°C</span>
|
|
</div>
|
|
<div class="iot-row">
|
|
<span>💧 Humidité</span>
|
|
<span class="iot-value ocean">68%</span>
|
|
</div>
|
|
<div class="iot-row">
|
|
<span>⚡ CO₂</span>
|
|
<span class="iot-value orange">485 ppm</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="msg-time" style="margin-top:4px;">09:13</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- User message 2 -->
|
|
<div class="msg-row user">
|
|
<div class="msg-bullet"><div class="msg-bubble">Où puis-je trouver un parking proche ?</div></div>
|
|
</div>
|
|
|
|
<!-- Bot message 3 -->
|
|
<div class="msg-row bot">
|
|
<div class="msg-avatar">🤖</div>
|
|
<div style="max-width:92%;">
|
|
<div class="msg-bubble">Voici les parkings disponibles à proximité :</div>
|
|
<div class="inline-card" style="margin-top:8px;">
|
|
<div class="inline-card-header">
|
|
<div style="width:32px;height:32px;border-radius:10px;background:#E8EAF6;display:flex;align-items:center;justify-content:center;font-size:16px;">🅿️</div>
|
|
<div>Parkings proches · Centre-Ville</div>
|
|
</div>
|
|
<div class="inline-card-body">
|
|
<div class="iot-row">
|
|
<span>🏢 Parking Gudel</span>
|
|
<span class="iot-value green">42 places</span>
|
|
</div>
|
|
<div class="iot-row">
|
|
<span>🚗 Place Perrinon</span>
|
|
<span class="iot-value orange">12 places</span>
|
|
</div>
|
|
<div class="iot-row">
|
|
<span>🏛️ Parking Préfecture</span>
|
|
<span class="iot-value green">68 places</span>
|
|
</div>
|
|
<div class="iot-row">
|
|
<span>🎭 Parking CTC</span>
|
|
<span class="iot-value blue">24 places</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="msg-time" style="margin-top:4px;">09:14</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Typing indicator -->
|
|
<div class="typing-indicator">
|
|
<div class="typing-dot"></div>
|
|
<div class="typing-dot"></div>
|
|
<div class="typing-dot"></div>
|
|
</div>
|
|
|
|
<!-- Quick Replies -->
|
|
<div class="quick-replies">
|
|
<button class="qr-btn">🌤️ Météo</button>
|
|
<button class="qr-btn">🚌 TCO horaires</button>
|
|
<button class="qr-btn">🚗 Trafic</button>
|
|
<button class="qr-btn">🌱 Énergie</button>
|
|
<button class="qr-btn">📢 Signalement</button>
|
|
<button class="qr-btn">🍽️ Marché</button>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- Input -->
|
|
<div class="chat-input-row">
|
|
<style>
|
|
.chat-input-row { display:flex; gap:8px; align-items:center; padding:10px 12px; background:white; border-top:1px solid var(--neutral-200); }
|
|
/* Fixed viewport for PDF/iframe embedding */
|
|
html, body { width: 390px !important; height: 844px !important; overflow: hidden !important; margin: 0 !important; padding: 0 !important; }
|
|
</style>
|
|
<button style="width:38px;height:38px;border-radius:var(--radius-full);border:none;background:transparent;display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--primary-500);flex-shrink:0;">
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48"/></svg>
|
|
</button>
|
|
<input class="chat-input" type="text" placeholder="Écrire un message...">
|
|
<button class="send-btn">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bottom Nav -->
|
|
<div class="bottom-nav">
|
|
<div class="bottom-nav-item">
|
|
<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 active">
|
|
<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);">05 — AI Chat · Blue v2</div>
|
|
</div>
|
|
</body>
|
|
</html>
|