Tag progetto, @menzioni, appuntamenti da conversazioni

- Modello Tag con nome e colore, M2M su Conversazione
- Modello Appuntamento con luogo, note, partecipanti, link a Conversazione
- @menzioni nei commenti e aggiornamenti: @username → link al profilo
- Autocomplete JS per @menzioni nelle textarea
- Auto-data conversazioni (default=now)
- CRUD completo appuntamenti con permessi autore
- Appuntamenti in agenda, dashboard, dettaglio conversazione
- Crea riunione direttamente da una conversazione (pre-compila titolo e partecipanti)
- Admin: Tag, Appuntamento registrati
This commit is contained in:
automationkriz
2026-04-07 14:28:47 +00:00
parent 006bb24215
commit 09f51b1227
19 changed files with 828 additions and 12 deletions

View File

@@ -0,0 +1,80 @@
{% extends "diario/base.html" %}
{% block title %}{{ app.titolo }}{% endblock %}
{% block content %}
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="d-flex align-items-center mb-4 gap-3 fade-in">
<a href="{% url 'appuntamenti_lista' %}" class="btn btn-icon btn-outline-secondary">
<i class="bi bi-arrow-left" style="font-size:.85rem;"></i>
</a>
<h5 class="mb-0 fw-bold flex-grow-1">
<i class="bi bi-calendar-check me-2 text-warning"></i>{{ app.titolo }}
</h5>
{% if can_edit %}
<a href="{% url 'appuntamento_modifica' app.pk %}" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-pencil me-1"></i>Modifica
</a>
<a href="{% url 'appuntamento_elimina' app.pk %}" class="btn btn-sm btn-outline-danger">
<i class="bi bi-trash me-1"></i>Elimina
</a>
{% endif %}
</div>
<div class="card p-4 mb-4 fade-in appuntamento-card">
<div class="row g-3 mb-3">
<div class="col-sm-6">
<small class="text-muted fw-semibold d-block mb-1">Data e ora</small>
<div class="fw-semibold">
<i class="bi bi-clock me-1 text-warning"></i>
{{ app.data_ora|date:"d/m/Y \a\l\l\e H:i" }}
</div>
{% if app.is_passato %}
<span class="countdown countdown-urgent mt-1">Passato</span>
{% endif %}
</div>
{% if app.luogo %}
<div class="col-sm-6">
<small class="text-muted fw-semibold d-block mb-1">Luogo</small>
<div><i class="bi bi-geo-alt me-1 text-muted"></i>{{ app.luogo }}</div>
</div>
{% endif %}
</div>
{% if app.note %}
<hr class="soft">
<small class="text-muted fw-semibold d-block mb-2">Note</small>
<div style="white-space:pre-wrap;line-height:1.8;font-size:.93rem;">{{ app.note }}</div>
{% endif %}
{% if app.conversazione %}
<hr class="soft">
<small class="text-muted fw-semibold d-block mb-2">Conversazione collegata</small>
<a href="{% url 'conversazione_dettaglio' app.conversazione.pk %}" class="text-decoration-none">
<i class="bi bi-chat-quote me-1"></i>{{ app.conversazione.titolo }}
</a>
{% endif %}
{% if app.partecipanti.all %}
<hr class="soft mt-3 mb-3">
<small class="text-muted fw-semibold d-block mb-2">Partecipanti</small>
<div class="d-flex flex-wrap gap-2">
{% for p in app.partecipanti.all %}
<div class="d-flex align-items-center gap-1 px-2 py-1 rounded-pill" style="background:#f0f2f5; font-size:.8rem;">
<span class="avatar" style="width:20px;height:20px;font-size:.55rem;">{{ p.username|slice:":2"|upper }}</span>
{{ p.get_full_name|default:p.username }}
</div>
{% endfor %}
</div>
{% endif %}
<hr class="soft mt-3 mb-2">
<small class="text-muted">
Creato da {{ app.creato_da.get_full_name|default:app.creato_da.username }} il {{ app.data_creazione|date:"d/m/Y H:i" }}
</small>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,63 @@
{% 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 'appuntamenti_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.data_ora.label }}</label>
{{ form.data_ora }}
</div>
<div class="mb-3">
<label class="form-label fw-semibold">{{ form.luogo.label }}</label>
{{ form.luogo }}
</div>
<div class="mb-3">
<label class="form-label fw-semibold">{{ form.note.label }}</label>
{{ form.note }}
</div>
<div class="mb-4">
<label class="form-label fw-semibold">{{ form.partecipanti.label }}</label>
<div class="row row-cols-2 row-cols-sm-3 g-2 mt-1">
{% for checkbox in form.partecipanti %}
<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>
{% if conversazione_pk %}
<input type="hidden" name="conversazione" value="{{ conversazione_pk }}">
{% endif %}
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">Salva</button>
{% if app %}
<a href="{% url 'appuntamento_dettaglio' app.pk %}" class="btn btn-outline-secondary">Annulla</a>
{% else %}
<a href="{% url 'appuntamenti_lista' %}" class="btn btn-outline-secondary">Annulla</a>
{% endif %}
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,69 @@
{% extends "diario/base.html" %}
{% block title %}Appuntamenti{% endblock %}
{% block content %}
<div class="d-flex justify-content-between align-items-center mb-4 fade-in">
<p class="section-title mb-0">Appuntamenti e Riunioni</p>
<a href="{% url 'appuntamento_nuovo' %}" class="btn btn-primary btn-sm px-3">
<i class="bi bi-plus-lg me-1"></i>Nuovo appuntamento
</a>
</div>
{% if futuri %}
<p class="section-title fade-in"><i class="bi bi-calendar-event me-1"></i>Prossimi</p>
{% for app in futuri %}
<div class="card mb-2 p-3 fade-in appuntamento-card">
<div class="d-flex justify-content-between align-items-start gap-3">
<div class="flex-grow-1">
<a href="{% url 'appuntamento_dettaglio' app.pk %}" class="text-decoration-none text-dark fw-semibold d-block mb-1">
<i class="bi bi-calendar-check me-1 text-warning"></i>{{ app.titolo }}
</a>
<div class="d-flex align-items-center gap-3 flex-wrap">
<small class="text-muted"><i class="bi bi-clock me-1"></i>{{ app.data_ora|date:"d/m/Y H:i" }}</small>
{% if app.luogo %}
<small class="text-muted"><i class="bi bi-geo-alt me-1"></i>{{ app.luogo }}</small>
{% endif %}
{% if app.conversazione %}
<small class="text-muted">
<i class="bi bi-link-45deg me-1"></i>
<a href="{% url 'conversazione_dettaglio' app.conversazione.pk %}" class="text-muted">{{ app.conversazione.titolo|truncatewords:5 }}</a>
</small>
{% endif %}
{% if app.partecipanti.count > 0 %}
<small class="text-muted"><i class="bi bi-people me-1"></i>{{ app.partecipanti.count }}</small>
{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
{% endif %}
{% if passati %}
<p class="section-title mt-4 fade-in"><i class="bi bi-clock-history me-1"></i>Passati</p>
{% for app in passati %}
<div class="card mb-2 p-3 fade-in" style="opacity:.7;">
<div class="d-flex justify-content-between align-items-start gap-3">
<div class="flex-grow-1">
<a href="{% url 'appuntamento_dettaglio' app.pk %}" class="text-decoration-none text-muted fw-semibold d-block mb-1">
<i class="bi bi-calendar-x me-1"></i>{{ app.titolo }}
</a>
<div class="d-flex align-items-center gap-3 flex-wrap">
<small class="text-muted"><i class="bi bi-clock me-1"></i>{{ app.data_ora|date:"d/m/Y H:i" }}</small>
{% if app.luogo %}
<small class="text-muted"><i class="bi bi-geo-alt me-1"></i>{{ app.luogo }}</small>
{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
{% endif %}
{% if not futuri and not passati %}
<div class="empty-state">
<i class="bi bi-calendar-plus"></i>
<p>Nessun appuntamento registrato.</p>
</div>
{% endif %}
{% endblock %}