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:
174
templates/diario/obiettivi/dettaglio.html
Normal file
174
templates/diario/obiettivi/dettaglio.html
Normal file
@@ -0,0 +1,174 @@
|
||||
{% extends "diario/base.html" %}
|
||||
{% load custom_filters %}
|
||||
{% block title %}{{ obj.titolo }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="d-flex align-items-center mb-4 gap-3 fade-in">
|
||||
<a href="{% url 'obiettivi_lista' %}" class="btn btn-icon btn-outline-secondary">
|
||||
<i class="bi bi-arrow-left" style="font-size:.85rem;"></i>
|
||||
</a>
|
||||
<div class="flex-grow-1">
|
||||
<h5 class="mb-0 fw-bold">{{ obj.titolo }}</h5>
|
||||
</div>
|
||||
<span class="badge-stato stato-{{ obj.stato }}">{{ obj.get_stato_display }}</span>
|
||||
{% if can_edit %}
|
||||
<a href="{% url 'obiettivo_modifica' obj.pk %}" class="btn btn-sm btn-outline-secondary">
|
||||
<i class="bi bi-pencil me-1"></i>Modifica
|
||||
</a>
|
||||
<a href="{% url 'obiettivo_elimina' obj.pk %}" class="btn btn-sm btn-outline-danger">
|
||||
<i class="bi bi-trash me-1"></i>Elimina
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Card info -->
|
||||
<div class="card p-4 mb-4 fade-in">
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-6 col-sm-3">
|
||||
<small class="text-muted d-block mb-1">Tipo</small>
|
||||
<span class="pill-tipo pill-{{ obj.tipo }}">{{ obj.get_tipo_display }}</span>
|
||||
</div>
|
||||
{% if obj.assegnato_a.all %}
|
||||
<div class="col-6 col-sm-3">
|
||||
<small class="text-muted d-block mb-1">Assegnato a</small>
|
||||
<div class="d-flex flex-wrap gap-1">
|
||||
{% for p in obj.assegnato_a.all %}
|
||||
<div class="d-flex align-items-center gap-1">
|
||||
<span class="avatar" style="width:22px;height:22px;font-size:.58rem;">{{ p.username|slice:":2"|upper }}</span>
|
||||
<span class="small fw-semibold">{{ p.get_full_name|default:p.username }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if obj.data_scadenza %}
|
||||
<div class="col-6 col-sm-3">
|
||||
<small class="text-muted d-block mb-1">Scadenza</small>
|
||||
<strong class="small">{{ obj.data_scadenza|date:"d/m/Y" }}</strong>
|
||||
{% with days=obj.giorni_rimanenti %}
|
||||
{% if days is not None %}
|
||||
<div class="mt-1">
|
||||
{% if days < 0 %}
|
||||
<span class="countdown countdown-urgent"><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">Scade oggi!</span>
|
||||
{% elif days == 1 %}
|
||||
<span class="countdown countdown-urgent">Scade domani</span>
|
||||
{% elif days <= 7 %}
|
||||
<span class="countdown countdown-soon">{{ days }} giorni rimasti</span>
|
||||
{% else %}
|
||||
<span class="countdown countdown-ok">{{ days }} giorni rimasti</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="col-6 col-sm-3">
|
||||
<small class="text-muted d-block mb-1">Creato da</small>
|
||||
<span class="small">{{ obj.creato_da.get_full_name|default:obj.creato_da.username }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if obj.descrizione %}
|
||||
<hr class="soft my-3">
|
||||
<p class="mb-0" style="white-space:pre-wrap;line-height:1.7;">{{ obj.descrizione }}</p>
|
||||
{% endif %}
|
||||
|
||||
<!-- Slider avanzamento AJAX -->
|
||||
<hr class="soft my-3">
|
||||
<div>
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<small class="fw-semibold text-muted">Avanzamento</small>
|
||||
<span class="slider-label" id="lbl-{{ obj.pk }}">{{ obj.avanzamento }}%</span>
|
||||
</div>
|
||||
<input
|
||||
type="range" min="0" max="100" step="5"
|
||||
value="{{ obj.avanzamento }}"
|
||||
class="progress-slider w-100"
|
||||
style="--val: {{ obj.avanzamento }}%;"
|
||||
data-url="{% url 'obiettivo_avanzamento_ajax' obj.pk %}"
|
||||
data-id="{{ obj.pk }}"
|
||||
>
|
||||
<span class="slider-saving d-none" id="saving-{{ obj.pk }}">salvataggio…</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Documenti allegati -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-3 fade-in">
|
||||
<p class="section-title mb-0"><i class="bi bi-paperclip me-1"></i>Allegati ({{ documenti|length }})</p>
|
||||
<a href="{% url 'documento_nuovo' %}?obiettivo={{ obj.pk }}" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-upload me-1"></i>Allega PDF
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% for doc in documenti %}
|
||||
<div class="card mb-2 p-3 fade-in">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<i class="bi bi-file-earmark-pdf" style="font-size:1.5rem;color:#dc3545;"></i>
|
||||
<div class="flex-grow-1">
|
||||
<a href="{% url 'documento_dettaglio' doc.pk %}" class="text-decoration-none text-dark fw-semibold small d-block">
|
||||
{{ doc.titolo }}
|
||||
</a>
|
||||
<small class="text-muted">{{ doc.caricato_da.get_full_name|default:doc.caricato_da.username }} · {{ doc.data_caricamento|date:"d/m/Y" }}</small>
|
||||
</div>
|
||||
<a href="{{ doc.file.url }}" class="btn btn-sm btn-outline-secondary" hx-boost="false" target="_blank">
|
||||
<i class="bi bi-download"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-muted small text-center py-2 fade-in">Nessun documento allegato.</p>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Aggiornamenti -->
|
||||
<p class="section-title mt-4 fade-in">Aggiornamenti ({{ aggiornamenti.count }})</p>
|
||||
|
||||
<!-- Form nuovo aggiornamento -->
|
||||
<div class="card p-3 mb-4 fade-in" style="border-left: 3px solid var(--accent) !important;">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<label class="form-label fw-semibold small mb-2">Aggiungi un aggiornamento</label>
|
||||
{{ agg_form.testo }}
|
||||
<button type="submit" class="btn btn-primary btn-sm mt-2 px-3">
|
||||
<i class="bi bi-send me-1"></i>Pubblica
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% for agg in aggiornamenti %}
|
||||
<div class="card mb-2 p-3 fade-in">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<span class="avatar" style="width:28px;height:28px;font-size:.65rem;">{{ agg.autore.username|slice:":2"|upper }}</span>
|
||||
<span class="fw-semibold small">{{ agg.autore.get_full_name|default:agg.autore.username }}</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<small class="text-muted">{{ agg.data|date:"d/m/Y H:i" }}</small>
|
||||
{% if user == agg.autore or user.is_superuser %}
|
||||
<div class="dropdown">
|
||||
<button class="btn btn-icon btn-outline-secondary" style="width:24px;height:24px;" data-bs-toggle="dropdown">
|
||||
<i class="bi bi-three-dots" style="font-size:.7rem;"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow border-0" style="border-radius:10px; min-width:140px;">
|
||||
<li><a class="dropdown-item small" href="{% url 'aggiornamento_modifica' agg.pk %}"><i class="bi bi-pencil me-2 text-muted"></i>Modifica</a></li>
|
||||
<li><a class="dropdown-item small text-danger" href="{% url 'aggiornamento_elimina' agg.pk %}"><i class="bi bi-trash me-2"></i>Elimina</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<p class="mb-0 mt-2 small" style="white-space:pre-wrap;line-height:1.65;padding-left:40px;">{{ agg.testo }}</p>
|
||||
</div>
|
||||
{% empty %}
|
||||
<p class="text-muted small text-center py-3">Nessun aggiornamento ancora.</p>
|
||||
{% endfor %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
67
templates/diario/obiettivi/form.html
Normal file
67
templates/diario/obiettivi/form.html
Normal file
@@ -0,0 +1,67 @@
|
||||
{% extends "diario/base.html" %}
|
||||
{% block title %}{{ titolo_pagina }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
<a href="{% url 'obiettivi_lista' %}" class="btn btn-sm btn-outline-secondary me-3">
|
||||
<i class="bi bi-arrow-left"></i>
|
||||
</a>
|
||||
<h4 class="mb-0">{{ titolo_pagina }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="card p-4">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">{{ form.titolo.label }}</label>
|
||||
{{ form.titolo }}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-semibold">{{ form.descrizione.label }}</label>
|
||||
{{ form.descrizione }}
|
||||
</div>
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-sm-4">
|
||||
<label class="form-label fw-semibold">{{ form.tipo.label }}</label>
|
||||
{{ form.tipo }}
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<label class="form-label fw-semibold">{{ form.stato.label }}</label>
|
||||
{{ form.stato }}
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<label class="form-label fw-semibold">{{ form.data_scadenza.label }}</label>
|
||||
{{ form.data_scadenza }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-semibold">{{ form.assegnato_a.label }}</label>
|
||||
<div class="form-text mb-2">Seleziona una o più persone (solo per obiettivi individuali).</div>
|
||||
<div class="row row-cols-2 row-cols-sm-3 g-2">
|
||||
{% for checkbox in form.assegnato_a %}
|
||||
<div class="col">
|
||||
<div class="form-check">
|
||||
{{ checkbox.tag }}
|
||||
<label class="form-check-label" for="{{ checkbox.id_for_label }}">
|
||||
{{ checkbox.choice_label }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">Salva</button>
|
||||
{% if obj %}
|
||||
<a href="{% url 'obiettivo_dettaglio' obj.pk %}" class="btn btn-outline-secondary">Annulla</a>
|
||||
{% else %}
|
||||
<a href="{% url 'obiettivi_lista' %}" class="btn btn-outline-secondary">Annulla</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
101
templates/diario/obiettivi/lista.html
Normal file
101
templates/diario/obiettivi/lista.html
Normal file
@@ -0,0 +1,101 @@
|
||||
{% extends "diario/base.html" %}
|
||||
{% load custom_filters %}
|
||||
{% block title %}Obiettivi{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4 fade-in">
|
||||
<div>
|
||||
<p class="section-title mb-0">Obiettivi del progetto</p>
|
||||
</div>
|
||||
<a href="{% url 'obiettivo_nuovo' %}" class="btn btn-primary btn-sm px-3">
|
||||
<i class="bi bi-plus-lg me-1"></i>Nuovo obiettivo
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Filtri -->
|
||||
<div class="mb-4 d-flex gap-2 flex-wrap fade-in">
|
||||
<a href="?filtro=tutti" class="btn btn-sm {% if filtro == 'tutti' %}btn-primary{% else %}btn-outline-secondary{% endif %}">Tutti</a>
|
||||
<a href="?filtro=collettivi" class="btn btn-sm {% if filtro == 'collettivi' %}btn-primary{% else %}btn-outline-secondary{% endif %}">Collettivi</a>
|
||||
<a href="?filtro=individuali" class="btn btn-sm {% if filtro == 'individuali' %}btn-primary{% else %}btn-outline-secondary{% endif %}">Individuali</a>
|
||||
<a href="?filtro=miei" class="btn btn-sm {% if filtro == 'miei' %}btn-primary{% else %}btn-outline-secondary{% endif %}">I miei</a>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
{% for obj in obiettivi %}
|
||||
<div class="col-12 col-md-6 col-xl-4">
|
||||
<div class="card h-100 p-3 fade-in">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<div class="d-flex gap-2 align-items-center flex-wrap">
|
||||
<span class="badge-stato stato-{{ obj.stato }}">{{ obj.get_stato_display }}</span>
|
||||
<span class="pill-tipo pill-{{ obj.tipo }}">{{ obj.get_tipo_display }}</span>
|
||||
</div>
|
||||
{% if user == obj.creato_da or user.is_superuser %}
|
||||
<a href="{% url 'obiettivo_modifica' obj.pk %}" class="btn btn-icon btn-outline-secondary">
|
||||
<i class="bi bi-pencil" style="font-size:.75rem;"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<a href="{% url 'obiettivo_dettaglio' obj.pk %}" class="text-decoration-none text-dark fw-semibold mb-1 d-block">
|
||||
{{ obj.titolo }}
|
||||
</a>
|
||||
|
||||
{% if obj.descrizione %}
|
||||
<p class="text-muted small mb-2">{{ obj.descrizione|truncatewords:18 }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if obj.assegnato_a.all %}
|
||||
<div class="d-flex align-items-center gap-1 flex-wrap mb-2">
|
||||
{% for p in obj.assegnato_a.all %}
|
||||
<span class="avatar" style="width:22px;height:22px;font-size:.58rem;" title="{{ p.get_full_name|default:p.username }}">{{ p.username|slice:":2"|upper }}</span>
|
||||
{% endfor %}
|
||||
<small class="text-muted ms-1">{% for p in obj.assegnato_a.all %}{{ p.get_full_name|default:p.username }}{% if not forloop.last %}, {% endif %}{% endfor %}</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Progress bar + slider AJAX -->
|
||||
<div class="mt-auto pt-2">
|
||||
<div class="d-flex align-items-center gap-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>
|
||||
{% if obj.data_scadenza %}
|
||||
<div class="d-flex align-items-center gap-2 mt-1">
|
||||
<small class="text-muted"><i class="bi bi-calendar3 me-1"></i>{{ obj.data_scadenza|date:"d/m/Y" }}</small>
|
||||
{% with days=obj.giorni_rimanenti %}
|
||||
{% if days is not None %}
|
||||
{% if days < 0 %}
|
||||
<span class="countdown countdown-urgent" style="font-size:.65rem;">Scaduto</span>
|
||||
{% elif days <= 3 %}
|
||||
<span class="countdown countdown-urgent" style="font-size:.65rem;">{{ days }}gg</span>
|
||||
{% elif days <= 7 %}
|
||||
<span class="countdown countdown-soon" style="font-size:.65rem;">{{ days }}gg</span>
|
||||
{% else %}
|
||||
<span class="countdown countdown-ok" style="font-size:.65rem;">{{ days }}gg</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="col-12">
|
||||
<div class="empty-state">
|
||||
<i class="bi bi-bullseye"></i>
|
||||
<p>Nessun obiettivo trovato per questo filtro.</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user