Initial commit: Diario Conversazioni Olimpic Nastri

- Django 5.2 + PostgreSQL + Gunicorn
- Conversazioni, Obiettivi, Documenti PDF, Persone
- Commenti e aggiornamenti con modifica/eliminazione
- Agenda, ricerca live, giorni rimanenti scadenze
- Bootstrap 5 + HTMX + toast notifications
- Deploy: Nginx + Gunicorn + SSL
This commit is contained in:
automationkriz
2026-04-05 14:48:22 +00:00
commit d296353dcb
48 changed files with 3538 additions and 0 deletions

View File

@@ -0,0 +1,237 @@
{% extends "diario/base.html" %}
{% load custom_filters %}
{% block title %}Dashboard Olimpic Nastri{% endblock %}
{% block content %}
<div class="row g-4">
<!-- ── Colonna principale: Timeline ── -->
<div class="col-lg-7">
<div class="d-flex justify-content-between align-items-center mb-3 fade-in">
<p class="section-title mb-0">Cronologia attività</p>
<a href="{% url 'conversazione_nuova' %}" class="btn btn-primary btn-sm px-3">
<i class="bi bi-plus-lg me-1"></i>Nuova conversazione
</a>
</div>
<div class="timeline">
{% for evento in eventi %}
<div class="tl-item fade-in">
{% if evento.tipo == 'conversazione' %}
<span class="tl-dot conv"></span>
<div class="card p-3">
<div class="d-flex justify-content-between align-items-start">
<div>
<span class="badge bg-primary bg-opacity-10 text-primary pill-tipo mb-1">
<i class="bi bi-chat-quote me-1"></i>Conversazione
</span>
<div class="fw-semibold">
<a href="{% url 'conversazione_dettaglio' evento.obj.pk %}" class="text-decoration-none text-dark">
{{ evento.obj.titolo }}
</a>
</div>
</div>
<small class="text-muted text-nowrap ms-2">{{ evento.data|date:"d/m H:i" }}</small>
</div>
<p class="text-muted small mb-1 mt-2">{{ evento.obj.contenuto|truncatewords:25 }}</p>
<div class="d-flex align-items-center gap-2 mt-1">
<span class="avatar" style="width:22px;height:22px;font-size:.62rem;">
{{ evento.obj.registrato_da.username|slice:":2"|upper }}
</span>
<small class="text-muted">{{ evento.obj.registrato_da.get_full_name|default:evento.obj.registrato_da.username }}</small>
{% if evento.obj.partecipanti.count > 0 %}
<small class="text-muted">· {{ evento.obj.partecipanti.count }} partecipanti</small>
{% endif %}
</div>
</div>
{% elif evento.tipo == 'documento' %}
<span class="tl-dot doc"></span>
<div class="card p-3">
<div class="d-flex justify-content-between align-items-start">
<div>
<span class="badge mb-1 pill-tipo" style="background:#ecfdf5;color:#059669;">
<i class="bi bi-file-earmark-pdf me-1"></i>Documento
</span>
<div class="fw-semibold">
<a href="{% url 'documento_dettaglio' evento.obj.pk %}" class="text-decoration-none text-dark">
{{ evento.obj.titolo }}
</a>
</div>
</div>
<small class="text-muted text-nowrap ms-2">{{ evento.data|date:"d/m H:i" }}</small>
</div>
{% if evento.obj.descrizione %}
<p class="text-muted small mb-1 mt-2">{{ evento.obj.descrizione|truncatewords:20 }}</p>
{% endif %}
<div class="d-flex align-items-center gap-2 mt-1">
<span class="avatar" style="width:22px;height:22px;font-size:.62rem;">
{{ evento.obj.caricato_da.username|slice:":2"|upper }}
</span>
<small class="text-muted">{{ evento.obj.caricato_da.get_full_name|default:evento.obj.caricato_da.username }}</small>
</div>
</div>
{% else %}
<span class="tl-dot agg"></span>
<div class="card p-3">
<div class="d-flex justify-content-between align-items-start">
<div>
<span class="badge mb-1 pill-tipo" style="background:#ffedd5;color:#c2410c;">
<i class="bi bi-arrow-up-circle me-1"></i>Aggiornamento obiettivo
</span>
<div class="fw-semibold">
<a href="{% url 'obiettivo_dettaglio' evento.obj.obiettivo.pk %}" class="text-decoration-none text-dark">
{{ evento.obj.obiettivo.titolo }}
</a>
</div>
</div>
<small class="text-muted text-nowrap ms-2">{{ evento.data|date:"d/m H:i" }}</small>
</div>
<p class="text-muted small mb-1 mt-2">{{ evento.obj.testo|truncatewords:25 }}</p>
<div class="d-flex align-items-center gap-2 mt-1">
<span class="avatar" style="width:22px;height:22px;font-size:.62rem;">
{{ evento.obj.autore.username|slice:":2"|upper }}
</span>
<small class="text-muted">{{ evento.obj.autore.get_full_name|default:evento.obj.autore.username }}</small>
</div>
</div>
{% endif %}
</div>
{% empty %}
<div class="empty-state">
<i class="bi bi-hourglass"></i>
<p>Nessuna attività ancora. Inizia registrando una conversazione.</p>
</div>
{% endfor %}
</div>
</div>
<!-- ── Sidebar destra ── -->
<div class="col-lg-5">
<!-- Obiettivi aperti con slider AJAX -->
<div class="card mb-4 fade-in">
<div class="card-header-accent d-flex justify-content-between align-items-center">
<span><i class="bi bi-bullseye me-2"></i>Obiettivi in corso</span>
<a href="{% url 'obiettivi_lista' %}" class="small fw-normal" style="color:var(--accent);">Vedi tutti →</a>
</div>
<div class="p-3">
{% for obj in obiettivi_aperti %}
<div class="mb-3 {% if not forloop.last %}pb-3 border-bottom{% endif %}">
<div class="d-flex justify-content-between align-items-center mb-1">
<a href="{% url 'obiettivo_dettaglio' obj.pk %}" class="text-decoration-none text-dark fw-semibold small">
{{ obj.titolo }}
</a>
<span class="badge-stato stato-{{ obj.stato }}">{{ obj.get_stato_display }}</span>
</div>
{% if obj.data_scadenza %}
<div class="mb-1">
{% with days=obj.giorni_rimanenti %}
{% if days is not None %}
{% if days < 0 %}
<span class="countdown countdown-urgent" style="font-size:.65rem;"><i class="bi bi-exclamation-triangle-fill me-1"></i>Scaduto da {{ days|abs_val }} gg</span>
{% elif days == 0 %}
<span class="countdown countdown-urgent" style="font-size:.65rem;">Scade oggi!</span>
{% elif days <= 3 %}
<span class="countdown countdown-urgent" style="font-size:.65rem;">{{ days }} gg rimasti</span>
{% elif days <= 7 %}
<span class="countdown countdown-soon" style="font-size:.65rem;">{{ days }} gg rimasti</span>
{% else %}
<span class="countdown countdown-ok" style="font-size:.65rem;">{{ days }} gg · {{ obj.data_scadenza|date:"d/m" }}</span>
{% endif %}
{% endif %}
{% endwith %}
</div>
{% endif %}
<!-- Slider AJAX -->
<div class="d-flex align-items-center gap-2 mt-2">
<input
type="range" min="0" max="100" step="5"
value="{{ obj.avanzamento }}"
class="progress-slider flex-grow-1"
style="--val: {{ obj.avanzamento }}%;"
data-url="{% url 'obiettivo_avanzamento_ajax' obj.pk %}"
data-id="{{ obj.pk }}"
>
<span class="slider-label" id="lbl-{{ obj.pk }}">{{ obj.avanzamento }}%</span>
</div>
<span class="slider-saving d-none" id="saving-{{ obj.pk }}">salvataggio…</span>
</div>
{% empty %}
<p class="text-muted small text-center py-3 mb-0">Tutti gli obiettivi sono completati!</p>
{% endfor %}
<div class="mt-2">
<a href="{% url 'obiettivo_nuovo' %}" class="btn btn-sm btn-outline-secondary w-100">
<i class="bi bi-plus-lg me-1"></i>Aggiungi obiettivo
</a>
</div>
</div>
</div>
<!-- Agenda rapida -->
<div class="card mb-4 fade-in">
<div class="card-header-accent d-flex justify-content-between align-items-center" style="background:#fff7ed; color:#c2410c; border-bottom-color:#fed7aa;">
<span><i class="bi bi-calendar-week me-2"></i>Prossime scadenze</span>
<a href="{% url 'agenda' %}" class="small fw-normal" style="color:#c2410c;">Agenda →</a>
</div>
<div class="p-0">
{% for obj in scadenze_prossime %}
<div class="d-flex align-items-center gap-3 px-3 py-2 {% if not forloop.last %}border-bottom{% endif %}">
<div class="text-center flex-shrink-0" style="width:36px;">
<div style="font-size:.85rem; font-weight:700; color:var(--accent);">{{ obj.data_scadenza|date:"d" }}</div>
<div style="font-size:.6rem; text-transform:uppercase; color:#94a3b8;">{{ obj.data_scadenza|date:"M" }}</div>
</div>
<div class="flex-grow-1">
<a href="{% url 'obiettivo_dettaglio' obj.pk %}" class="text-decoration-none text-dark fw-semibold small d-block">
{{ obj.titolo|truncatewords:8 }}
</a>
{% with days=obj.giorni_rimanenti %}
{% if days <= 3 %}
<span class="countdown countdown-urgent" style="font-size:.6rem;">{{ days }} gg</span>
{% elif days <= 7 %}
<span class="countdown countdown-soon" style="font-size:.6rem;">{{ days }} gg</span>
{% else %}
<span class="countdown countdown-ok" style="font-size:.6rem;">{{ days }} gg</span>
{% endif %}
{% endwith %}
</div>
<div style="width:50px;">
<div class="progress" style="height:4px; border-radius:2px;">
<div class="progress-bar" style="width:{{ obj.avanzamento }}%; background:var(--accent);"></div>
</div>
</div>
</div>
{% empty %}
<p class="text-muted small text-center py-3 mb-0">Nessuna scadenza prossima</p>
{% endfor %}
</div>
</div>
{% if scaduti %}
<!-- Scaduti -->
<div class="card fade-in">
<div class="card-header-accent d-flex align-items-center" style="background:#fef2f2; color:#dc2626; border-bottom-color:#fecaca;">
<i class="bi bi-exclamation-triangle me-2"></i>
<span>Obiettivi scaduti</span>
<span class="badge bg-danger bg-opacity-10 text-danger ms-2">{{ scaduti|length }}</span>
</div>
<div class="p-0">
{% for obj in scaduti %}
<div class="d-flex align-items-center gap-3 px-3 py-2 {% if not forloop.last %}border-bottom{% endif %}">
<i class="bi bi-exclamation-circle text-danger"></i>
<div class="flex-grow-1">
<a href="{% url 'obiettivo_dettaglio' obj.pk %}" class="text-decoration-none text-danger fw-semibold small d-block">
{{ obj.titolo|truncatewords:8 }}
</a>
<small class="text-muted">Scaduto il {{ obj.data_scadenza|date:"d/m/Y" }}</small>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
{% endblock %}