- Flask/SQLAlchemy app with profile, dashboard, recommendations - AI crawl pipeline (GPT-4o) with admin review workflow - 14 curated health sources (RSS + index crawling) - Production config: env vars, Gunicorn, systemd, nginx - deploy/ scripts for VPS setup and updates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
184 lines
8.0 KiB
HTML
184 lines
8.0 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}Tableau de bord — OpenCare{% endblock %}
|
|
{% block content %}
|
|
<div class="container py-4">
|
|
|
|
<!-- Header -->
|
|
<div class="row align-items-center mb-4">
|
|
<div class="col">
|
|
<h1 class="fw-bold h3 mb-1">
|
|
<i class="fas fa-chart-line text-primary me-2"></i>
|
|
Bonjour {% if profile.first_name %}<span class="text-primary">{{ profile.first_name }}</span>{% else %}!{% endif %}
|
|
</h1>
|
|
<p class="text-muted mb-0">{{ total }} recommandations personnalisées pour votre profil</p>
|
|
</div>
|
|
<div class="col-auto">
|
|
<a href="{{ url_for('profile') }}" class="btn btn-outline-primary">
|
|
<i class="fas fa-user-edit me-2"></i>Modifier le profil
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Profile summary bar -->
|
|
<div class="profile-summary-bar card border-0 shadow-sm mb-4">
|
|
<div class="card-body py-3 px-4">
|
|
<div class="row align-items-center g-3">
|
|
<div class="col-md-3">
|
|
<div class="d-flex align-items-center gap-3">
|
|
<div class="avatar-circle">
|
|
{{ (profile.first_name or 'U')[0].upper() }}
|
|
</div>
|
|
<div>
|
|
<div class="fw-semibold">{{ profile.first_name or '' }} {{ profile.last_name or '' }}</div>
|
|
<div class="text-muted small">{{ current_user.email }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="d-flex flex-wrap gap-2">
|
|
{% if profile.age %}
|
|
<span class="profile-badge"><i class="fas fa-birthday-cake me-1"></i>{{ profile.age }} ans</span>
|
|
{% endif %}
|
|
{% if profile.bmi %}
|
|
<span class="profile-badge profile-badge-{{ profile.bmi_category[1] }}">
|
|
<i class="fas fa-weight me-1"></i>IMC {{ profile.bmi }} — {{ profile.bmi_category[0] }}
|
|
</span>
|
|
{% endif %}
|
|
{% if profile.blood_type %}
|
|
<span class="profile-badge"><i class="fas fa-tint me-1"></i>Groupe {{ profile.blood_type }}</span>
|
|
{% endif %}
|
|
{% if profile.diet_type %}
|
|
<span class="profile-badge">
|
|
{% set diet_icons = {'omnivore':'🥩','flexitarian':'🥗','vegetarian':'🌿','vegan':'🌱','keto':'🥑','paleo':'🍖','mediterranean':'🫒'} %}
|
|
{{ diet_icons.get(profile.diet_type, '🍽️') }}
|
|
{{ {'omnivore':'Omnivore','flexitarian':'Flexitarien','vegetarian':'Végétarien','vegan':'Vegan','keto':'Keto','paleo':'Paléo','mediterranean':'Méditerranéen'}.get(profile.diet_type, profile.diet_type) }}
|
|
</span>
|
|
{% endif %}
|
|
{% if profile.activity_level %}
|
|
<span class="profile-badge">
|
|
{% set act_icons = {'sedentary':'🪑','light':'🚶','moderate':'🚴','intense':'🏋️','athlete':'🏆'} %}
|
|
{{ act_icons.get(profile.activity_level, '🏃') }}
|
|
{{ {'sedentary':'Sédentaire','light':'Actif léger','moderate':'Actif modéré','intense':'Très actif','athlete':'Athlète'}.get(profile.activity_level, profile.activity_level) }}
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="completeness-wrap">
|
|
<div class="d-flex justify-content-between small mb-1">
|
|
<span class="text-muted">Profil complété</span>
|
|
<span class="fw-semibold text-primary">{{ completeness }}%</span>
|
|
</div>
|
|
<div class="progress completeness-bar">
|
|
<div class="progress-bar bg-primary" style="width: {{ completeness }}%"></div>
|
|
</div>
|
|
{% if completeness < 70 %}
|
|
<a href="{{ url_for('profile') }}" class="small text-primary mt-1 d-inline-block">
|
|
<i class="fas fa-plus me-1"></i>Compléter pour plus de recommandations
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Legend -->
|
|
<div class="d-flex flex-wrap gap-3 mb-4 align-items-center">
|
|
<span class="text-muted small fw-semibold">Niveau de preuve :</span>
|
|
<span class="evidence-badge ev-A">A — Fort (méta-analyse / RCT)</span>
|
|
<span class="evidence-badge ev-B">B — Modéré (observationnel)</span>
|
|
<span class="evidence-badge ev-C">C — Expert consensus</span>
|
|
</div>
|
|
|
|
<!-- Recommendation domains -->
|
|
{% for domain_key, (domain_label, domain_icon), recs in ordered_domains %}
|
|
<div class="domain-section mb-5">
|
|
<div class="domain-header d-flex align-items-center mb-3">
|
|
<span class="domain-icon-lg me-3">{{ domain_icon }}</span>
|
|
<div>
|
|
<h4 class="fw-bold mb-0">{{ domain_label }}</h4>
|
|
<span class="text-muted small">{{ recs|length }} recommandation{{ 's' if recs|length > 1 else '' }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row g-3">
|
|
{% for rec in recs %}
|
|
<div class="col-md-6 col-lg-4">
|
|
<div class="rec-card card h-100 border-0 shadow-sm" data-bs-toggle="modal" data-bs-target="#modal-{{ rec.id }}">
|
|
<div class="card-body p-3">
|
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
<span class="rec-icon">{{ rec.icon }}</span>
|
|
<span class="evidence-badge ev-{{ rec.evidence_level }}">{{ rec.evidence_level }}</span>
|
|
</div>
|
|
<h6 class="fw-bold mb-2">{{ rec.title }}</h6>
|
|
<p class="text-muted small mb-3 rec-summary">{{ rec.summary }}</p>
|
|
<div class="d-flex flex-wrap gap-1">
|
|
{% for tag in rec.tags[:3] %}
|
|
<span class="tag-pill">{{ tag }}</span>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
<div class="card-footer bg-transparent border-0 pt-0 px-3 pb-3">
|
|
<button class="btn btn-sm btn-outline-primary w-100">
|
|
<i class="fas fa-book-open me-1"></i>Voir les détails
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modal for this rec -->
|
|
<div class="modal fade" id="modal-{{ rec.id }}" tabindex="-1">
|
|
<div class="modal-dialog modal-dialog-centered modal-lg">
|
|
<div class="modal-content border-0 shadow">
|
|
<div class="modal-header border-0 pb-0">
|
|
<div class="d-flex align-items-center gap-3">
|
|
<span class="rec-icon-lg">{{ rec.icon }}</span>
|
|
<div>
|
|
<h5 class="modal-title fw-bold mb-1">{{ rec.title }}</h5>
|
|
<span class="evidence-badge ev-{{ rec.evidence_level }} me-2">Niveau {{ rec.evidence_level }}</span>
|
|
<span class="badge bg-light text-dark">{{ rec.domain_label }}</span>
|
|
</div>
|
|
</div>
|
|
<button type="button" class="btn-close ms-auto" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body pt-3">
|
|
<div class="rec-detail-box mb-3">
|
|
<p class="mb-0">{{ rec.details }}</p>
|
|
</div>
|
|
<div class="source-box">
|
|
<i class="fas fa-graduation-cap text-primary me-2"></i>
|
|
<strong>Source :</strong> {{ rec.source }}
|
|
</div>
|
|
<div class="mt-3 d-flex flex-wrap gap-2">
|
|
{% for tag in rec.tags %}
|
|
<span class="tag-pill tag-pill-lg">{{ tag }}</span>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer border-0">
|
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Fermer</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
|
|
{% if not ordered_domains %}
|
|
<div class="empty-state text-center py-5">
|
|
<div class="empty-icon mb-4">🔍</div>
|
|
<h4 class="fw-bold">Aucune recommandation pour l'instant</h4>
|
|
<p class="text-muted mb-4">Complétez votre profil de santé pour recevoir des conseils personnalisés.</p>
|
|
<a href="{{ url_for('profile') }}" class="btn btn-primary btn-lg px-5">
|
|
<i class="fas fa-user-edit me-2"></i>Compléter mon profil
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
|
|
</div>
|
|
{% endblock %}
|