refactor: remove TeambuildingVote feature and associated views, templates, and tests
This commit is contained in:
parent
d9ee551e42
commit
526c8185fa
11 changed files with 20 additions and 1265 deletions
|
|
@ -1,15 +1,7 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from .models import HomeWidget, HomeWidgetConditionGroup, HomeWidgetCondition, TeambuildingVote
|
from .models import HomeWidget, HomeWidgetConditionGroup, HomeWidgetCondition
|
||||||
|
|
||||||
|
|
||||||
@admin.register(TeambuildingVote)
|
|
||||||
class TeambuildingVoteAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ("id", "voter_name", "vote_gael", "vote_charly", "vote_tequila", "vote_vivien", "created_at")
|
|
||||||
list_filter = ("created_at",)
|
|
||||||
search_fields = ("voter_name",)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class HomeWidgetConditionInline(admin.TabularInline):
|
class HomeWidgetConditionInline(admin.TabularInline):
|
||||||
|
|
|
||||||
|
|
@ -225,34 +225,6 @@ msgstr "Condition"
|
||||||
msgid "Conditions"
|
msgid "Conditions"
|
||||||
msgstr "Terms"
|
msgstr "Terms"
|
||||||
|
|
||||||
#: home/models.py:341
|
|
||||||
msgid "Nom (optionnel)"
|
|
||||||
msgstr "optional"
|
|
||||||
|
|
||||||
#: home/models.py:342
|
|
||||||
msgid "La journée 'Gaël Non Di Dju'"
|
|
||||||
msgstr "The 'Gaël Non Di Dju' day"
|
|
||||||
|
|
||||||
#: home/models.py:343
|
|
||||||
msgid "La journée 'La cité de Charly'"
|
|
||||||
msgstr "'The city of Charly' day"
|
|
||||||
|
|
||||||
#: home/models.py:344
|
|
||||||
msgid "La journée 'Tequila Cadena'"
|
|
||||||
msgstr "Tequila Cadena Day"
|
|
||||||
|
|
||||||
#: home/models.py:345
|
|
||||||
msgid "La journée 'Vivien de La Grande Propreté'"
|
|
||||||
msgstr "The 'Vivien de La Grande Propreté' day"
|
|
||||||
|
|
||||||
#: home/models.py:352
|
|
||||||
msgid "Vote Teambuilding"
|
|
||||||
msgstr "Teambuilding voting"
|
|
||||||
|
|
||||||
#: home/models.py:353
|
|
||||||
msgid "Votes Teambuilding"
|
|
||||||
msgstr "Teambuilding Votes"
|
|
||||||
|
|
||||||
#: home/templates/home/_add_repair_intervention_modal.html:9
|
#: home/templates/home/_add_repair_intervention_modal.html:9
|
||||||
msgid "Nouvelle intervention de réparation"
|
msgid "Nouvelle intervention de réparation"
|
||||||
msgstr "New repair intervention"
|
msgstr "New repair intervention"
|
||||||
|
|
|
||||||
|
|
@ -230,34 +230,6 @@ msgstr "État"
|
||||||
msgid "Conditions"
|
msgid "Conditions"
|
||||||
msgstr "Conditions"
|
msgstr "Conditions"
|
||||||
|
|
||||||
#: home/models.py:341
|
|
||||||
msgid "Nom (optionnel)"
|
|
||||||
msgstr "Nom (optionnel)"
|
|
||||||
|
|
||||||
#: home/models.py:342
|
|
||||||
msgid "La journée 'Gaël Non Di Dju'"
|
|
||||||
msgstr "La journée 'Gaël Non Di Dju'"
|
|
||||||
|
|
||||||
#: home/models.py:343
|
|
||||||
msgid "La journée 'La cité de Charly'"
|
|
||||||
msgstr "La journée 'La cité de Charly'"
|
|
||||||
|
|
||||||
#: home/models.py:344
|
|
||||||
msgid "La journée 'Tequila Cadena'"
|
|
||||||
msgstr "La journée 'Tequila Cadena'"
|
|
||||||
|
|
||||||
#: home/models.py:345
|
|
||||||
msgid "La journée 'Vivien de La Grande Propreté'"
|
|
||||||
msgstr "La journée 'Vivien de La Grande Propreté'"
|
|
||||||
|
|
||||||
#: home/models.py:352
|
|
||||||
msgid "Vote Teambuilding"
|
|
||||||
msgstr "Vote Teambuilding"
|
|
||||||
|
|
||||||
#: home/models.py:353
|
|
||||||
msgid "Votes Teambuilding"
|
|
||||||
msgstr "Votes Teambuilding"
|
|
||||||
|
|
||||||
#: home/templates/home/_add_repair_intervention_modal.html:9
|
#: home/templates/home/_add_repair_intervention_modal.html:9
|
||||||
msgid "Nouvelle intervention de réparation"
|
msgid "Nouvelle intervention de réparation"
|
||||||
msgstr "Nouvelle intervention de réparation"
|
msgstr "Nouvelle intervention de réparation"
|
||||||
|
|
|
||||||
|
|
@ -225,34 +225,6 @@ msgstr "Staat"
|
||||||
msgid "Conditions"
|
msgid "Conditions"
|
||||||
msgstr "Voorwaarden"
|
msgstr "Voorwaarden"
|
||||||
|
|
||||||
#: home/models.py:341
|
|
||||||
msgid "Nom (optionnel)"
|
|
||||||
msgstr "optioneel"
|
|
||||||
|
|
||||||
#: home/models.py:342
|
|
||||||
msgid "La journée 'Gaël Non Di Dju'"
|
|
||||||
msgstr "De 'Gaël Non Di Dju'-dag"
|
|
||||||
|
|
||||||
#: home/models.py:343
|
|
||||||
msgid "La journée 'La cité de Charly'"
|
|
||||||
msgstr "Dag 'De stad van Charly'"
|
|
||||||
|
|
||||||
#: home/models.py:344
|
|
||||||
msgid "La journée 'Tequila Cadena'"
|
|
||||||
msgstr "Tequila Cadena-dag"
|
|
||||||
|
|
||||||
#: home/models.py:345
|
|
||||||
msgid "La journée 'Vivien de La Grande Propreté'"
|
|
||||||
msgstr "De dag van 'Vivien de La Grande Propreté'"
|
|
||||||
|
|
||||||
#: home/models.py:352
|
|
||||||
msgid "Vote Teambuilding"
|
|
||||||
msgstr "Stemmen voor teambuilding"
|
|
||||||
|
|
||||||
#: home/models.py:353
|
|
||||||
msgid "Votes Teambuilding"
|
|
||||||
msgstr "Stemmen voor teambuilding"
|
|
||||||
|
|
||||||
#: home/templates/home/_add_repair_intervention_modal.html:9
|
#: home/templates/home/_add_repair_intervention_modal.html:9
|
||||||
msgid "Nouvelle intervention de réparation"
|
msgid "Nouvelle intervention de réparation"
|
||||||
msgstr "Nieuwe reparatie-interventie"
|
msgstr "Nieuwe reparatie-interventie"
|
||||||
|
|
|
||||||
16
streetup/home/migrations/0002_delete_teambuildingvote.py
Normal file
16
streetup/home/migrations/0002_delete_teambuildingvote.py
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Generated by Django 6.0.7 on 2026-08-12 15:40
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('home', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='TeambuildingVote',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -335,21 +335,3 @@ class HomeWidgetCondition(models.Model):
|
||||||
if self.is_negated:
|
if self.is_negated:
|
||||||
return not res
|
return not res
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
class TeambuildingVote(models.Model):
|
|
||||||
voter_name = models.CharField(max_length=100, blank=True, null=True, verbose_name=_("Nom (optionnel)"))
|
|
||||||
vote_gael = models.IntegerField(default=0, verbose_name=_("La journée 'Gaël Non Di Dju'"))
|
|
||||||
vote_charly = models.IntegerField(default=0, verbose_name=_("La journée 'La cité de Charly'"))
|
|
||||||
vote_tequila = models.IntegerField(default=0, verbose_name=_("La journée 'Tequila Cadena'"))
|
|
||||||
vote_vivien = models.IntegerField(default=0, verbose_name=_("La journée 'Vivien de La Grande Propreté'"))
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"Vote #{self.id} - {self.voter_name or 'Anonyme'}"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = _("Vote Teambuilding")
|
|
||||||
verbose_name_plural = _("Votes Teambuilding")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,594 +0,0 @@
|
||||||
{% load static %}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="fr">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Flexi Teambuilding - Vote</title>
|
|
||||||
<link rel="icon" href="{% static 'common/favicon.ico' %}" sizes="any">
|
|
||||||
|
|
||||||
<!-- Google Fonts -->
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=Plus+Jakarta+Sans:wght@300;400;500;700&display=swap" rel="stylesheet">
|
|
||||||
|
|
||||||
<!-- Bootstrap Icons (local) -->
|
|
||||||
<link href="{% static 'common/bootstrap-icons.min.css' %}" rel="stylesheet">
|
|
||||||
<!-- Bootstrap CSS (local) -->
|
|
||||||
<link href="{% static 'common/bootstrap.min.css' %}" rel="stylesheet">
|
|
||||||
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bg-gradient: linear-gradient(135deg, #0f172a 0%, #1e1b4b 50%, #311042 100%);
|
|
||||||
--card-bg: rgba(30, 41, 59, 0.45);
|
|
||||||
--card-border: rgba(255, 255, 255, 0.08);
|
|
||||||
--accent-color: #d946ef;
|
|
||||||
--accent-gradient: linear-gradient(90deg, #a855f7 0%, #d946ef 100%);
|
|
||||||
--text-main: #f8fafc;
|
|
||||||
--text-muted: #94a3b8;
|
|
||||||
--star-color: #fbbf24;
|
|
||||||
--star-empty: #475569;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
|
||||||
background: var(--bg-gradient);
|
|
||||||
background-attachment: fixed;
|
|
||||||
color: var(--text-main);
|
|
||||||
min-height: 100vh;
|
|
||||||
padding: 3rem 0;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2, h3, .heading-font {
|
|
||||||
font-family: 'Outfit', sans-serif;
|
|
||||||
font-weight: 800;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glass-header {
|
|
||||||
background: rgba(15, 23, 42, 0.4);
|
|
||||||
backdrop-filter: blur(16px);
|
|
||||||
border-bottom: 1px solid var(--card-border);
|
|
||||||
padding: 2rem;
|
|
||||||
border-radius: 24px;
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
font-size: 2.8rem;
|
|
||||||
background: linear-gradient(120deg, #c084fc, #f472b6, #60a5fa);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-subtitle {
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proposal-card {
|
|
||||||
background: var(--card-bg);
|
|
||||||
backdrop-filter: blur(12px);
|
|
||||||
border: 1px solid var(--card-border);
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: 2rem;
|
|
||||||
height: 100%;
|
|
||||||
transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.proposal-card::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 4px;
|
|
||||||
background: var(--accent-gradient);
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proposal-card:hover {
|
|
||||||
transform: translateY(-8px);
|
|
||||||
border-color: rgba(217, 70, 239, 0.3);
|
|
||||||
box-shadow: 0 12px 30px rgba(217, 70, 239, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.proposal-card:hover::before {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-number {
|
|
||||||
position: absolute;
|
|
||||||
right: 1.5rem;
|
|
||||||
top: 1rem;
|
|
||||||
font-size: 3.5rem;
|
|
||||||
font-weight: 900;
|
|
||||||
color: rgba(255, 255, 255, 0.03);
|
|
||||||
line-height: 1;
|
|
||||||
user-select: none;
|
|
||||||
font-family: 'Outfit', sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proposal-title {
|
|
||||||
font-size: 1.4rem;
|
|
||||||
color: #f1f5f9;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
padding-right: 2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
border-left: 3px solid var(--accent-color);
|
|
||||||
padding-left: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proposal-details {
|
|
||||||
list-style-type: none;
|
|
||||||
padding-left: 0;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proposal-details li {
|
|
||||||
position: relative;
|
|
||||||
padding-left: 1.5rem;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: #cbd5e1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proposal-details li::before {
|
|
||||||
content: "\F2E6"; /* Bootstrap Icon Chevron Right */
|
|
||||||
font-family: "bootstrap-icons";
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 2px;
|
|
||||||
color: var(--accent-color);
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-budget {
|
|
||||||
background: rgba(217, 70, 239, 0.15);
|
|
||||||
border: 1px solid rgba(217, 70, 239, 0.3);
|
|
||||||
color: #f472b6;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 0.4rem 0.8rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: auto;
|
|
||||||
align-self: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rating-wrapper {
|
|
||||||
background: rgba(15, 23, 42, 0.5);
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 1rem;
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rating-title {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
color: var(--text-muted);
|
|
||||||
letter-spacing: 1px;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stars-container {
|
|
||||||
display: inline-flex;
|
|
||||||
gap: 0.75rem;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.star-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
padding: 0;
|
|
||||||
font-size: 2.2rem;
|
|
||||||
color: var(--star-empty);
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.star-btn:hover {
|
|
||||||
transform: scale(1.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.star-btn.active {
|
|
||||||
color: var(--star-color);
|
|
||||||
text-shadow: 0 0 15px rgba(251, 191, 36, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.star-btn.hover {
|
|
||||||
color: rgba(251, 191, 36, 0.75);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rating-feedback {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: var(--text-muted);
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
font-weight: 500;
|
|
||||||
min-height: 1.25rem;
|
|
||||||
transition: color 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rating-feedback.rated {
|
|
||||||
color: #34d399;
|
|
||||||
}
|
|
||||||
|
|
||||||
.voter-identity-card {
|
|
||||||
background: var(--card-bg);
|
|
||||||
backdrop-filter: blur(12px);
|
|
||||||
border: 1px solid var(--card-border);
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: 2.5rem;
|
|
||||||
margin-top: 3rem;
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-voter-name {
|
|
||||||
background: rgba(15, 23, 42, 0.6);
|
|
||||||
border: 1px solid var(--card-border);
|
|
||||||
border-radius: 12px;
|
|
||||||
color: var(--text-main);
|
|
||||||
padding: 0.9rem 1.2rem;
|
|
||||||
font-size: 1.05rem;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-voter-name:focus {
|
|
||||||
background: rgba(15, 23, 42, 0.8);
|
|
||||||
border-color: var(--accent-color);
|
|
||||||
box-shadow: 0 0 0 3px rgba(217, 70, 239, 0.25);
|
|
||||||
color: var(--text-main);
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-submit-vote {
|
|
||||||
background: var(--accent-gradient);
|
|
||||||
border: none;
|
|
||||||
color: white;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 1.15rem;
|
|
||||||
padding: 0.9rem 2.5rem;
|
|
||||||
border-radius: 12px;
|
|
||||||
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
||||||
box-shadow: 0 4px 15px rgba(217, 70, 239, 0.4);
|
|
||||||
font-family: 'Outfit', sans-serif;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-submit-vote:hover {
|
|
||||||
transform: scale(1.03) translateY(-2px);
|
|
||||||
box-shadow: 0 8px 25px rgba(217, 70, 239, 0.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-submit-vote:active {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.reset-btn {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: var(--text-muted);
|
|
||||||
text-decoration: none;
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
padding: 2px 6px;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reset-btn:hover {
|
|
||||||
color: #ef4444;
|
|
||||||
background: rgba(239, 68, 68, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive styling tweak */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
body {
|
|
||||||
padding: 1.5rem 0;
|
|
||||||
}
|
|
||||||
.header-title {
|
|
||||||
font-size: 2.2rem;
|
|
||||||
}
|
|
||||||
.proposal-card {
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<!-- Header Block -->
|
|
||||||
<div class="glass-header">
|
|
||||||
<div class="card-number" style="left:1rem; right:auto; top:0.5rem;">FTB</div>
|
|
||||||
<h1 class="header-title">Flexi Teambuilding</h1>
|
|
||||||
<p class="header-subtitle">Exprimez votre avis sur les 4 propositions de journée pour le prochain Teambuilding !</p>
|
|
||||||
<div class="badge bg-secondary px-3 py-2 text-uppercase tracking-wider" style="font-size:0.75rem; border-radius:50px;">
|
|
||||||
<i class="bi bi-star-fill me-1 text-warning"></i> Vote : de 0 à 3 étoiles
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Main Vote Form -->
|
|
||||||
<form method="POST" id="voteForm" class="needs-validation" novalidate>
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<!-- Proposals Grid -->
|
|
||||||
<div class="row g-4">
|
|
||||||
|
|
||||||
<!-- Proposal 1: Gaël -->
|
|
||||||
<div class="col-lg-6 col-md-12">
|
|
||||||
<div class="proposal-card">
|
|
||||||
<div class="card-number">01</div>
|
|
||||||
<div>
|
|
||||||
<h3 class="proposal-title">La journée "Gaël Non Di Dju"</h3>
|
|
||||||
<ul class="proposal-details">
|
|
||||||
<li>RDV à Tour & Taxi à 10h pour un padel</li>
|
|
||||||
<li>Repas convivial dans les alentours</li>
|
|
||||||
<li>BattleKart de folie</li>
|
|
||||||
<li>Debrief autour d'un bon cocktail ou à la brasserie de la Senne</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
|
||||||
<span class="badge-budget">
|
|
||||||
<i class="bi bi-currency-euro me-1"></i> Budget : ~150 € / pers.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Rating Widget -->
|
|
||||||
<div class="rating-wrapper">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2 px-1">
|
|
||||||
<span class="rating-title">Votre évaluation</span>
|
|
||||||
<button type="button" class="reset-btn" onclick="resetRating('gael')">Effacer (0★)</button>
|
|
||||||
</div>
|
|
||||||
<div class="stars-container" data-proposal="gael">
|
|
||||||
<button type="button" class="star-btn" data-value="1"><i class="bi bi-star-fill"></i></button>
|
|
||||||
<button type="button" class="star-btn" data-value="2"><i class="bi bi-star-fill"></i></button>
|
|
||||||
<button type="button" class="star-btn" data-value="3"><i class="bi bi-star-fill"></i></button>
|
|
||||||
</div>
|
|
||||||
<input type="hidden" name="vote_gael" id="input_gael" value="0">
|
|
||||||
<div class="rating-feedback" id="feedback_gael">Pas encore voté</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Proposal 2: Charly -->
|
|
||||||
<div class="col-lg-6 col-md-12">
|
|
||||||
<div class="proposal-card">
|
|
||||||
<div class="card-number">02</div>
|
|
||||||
<div>
|
|
||||||
<h3 class="proposal-title">La journée "La cité de Charly"</h3>
|
|
||||||
<ul class="proposal-details">
|
|
||||||
<li>RDV vers 10h30 à la station Demey</li>
|
|
||||||
<li>Petit dèj et balade dans les superbes cités jardin de Watermael-Boitsfort</li>
|
|
||||||
<li>En souvenir de notre jeunesse, on prend le bus (dingue !!!)</li>
|
|
||||||
<li>Repas convivial aux alentours du cimetière d'Ixelles, au feeling du jour</li>
|
|
||||||
<li>Escape game palpitant à Ixelles</li>
|
|
||||||
<li>Debriefing dans un rade local, ou évaluation des améliorations de la DGI sur base de la place Flagey</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
|
||||||
<span class="badge-budget">
|
|
||||||
<i class="bi bi-currency-euro me-1"></i> Budget : ~100 € / pers.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Rating Widget -->
|
|
||||||
<div class="rating-wrapper">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2 px-1">
|
|
||||||
<span class="rating-title">Votre évaluation</span>
|
|
||||||
<button type="button" class="reset-btn" onclick="resetRating('charly')">Effacer (0★)</button>
|
|
||||||
</div>
|
|
||||||
<div class="stars-container" data-proposal="charly">
|
|
||||||
<button type="button" class="star-btn" data-value="1"><i class="bi bi-star-fill"></i></button>
|
|
||||||
<button type="button" class="star-btn" data-value="2"><i class="bi bi-star-fill"></i></button>
|
|
||||||
<button type="button" class="star-btn" data-value="3"><i class="bi bi-star-fill"></i></button>
|
|
||||||
</div>
|
|
||||||
<input type="hidden" name="vote_charly" id="input_charly" value="0">
|
|
||||||
<div class="rating-feedback" id="feedback_charly">Pas encore voté</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Proposal 3: Tequila -->
|
|
||||||
<div class="col-lg-6 col-md-12">
|
|
||||||
<div class="proposal-card">
|
|
||||||
<div class="card-number">03</div>
|
|
||||||
<div>
|
|
||||||
<h3 class="proposal-title">La journée "Tequila Cadena"</h3>
|
|
||||||
<ul class="proposal-details">
|
|
||||||
<li>10h30 : Tequila en attendant la retardataire, puis 10h40 : Tequila pour bien débuter la journée</li>
|
|
||||||
<li>10h45 : Exercices fun de théâtre et d'improvisation</li>
|
|
||||||
<li>Tequila pour l'apéro, suivi d'un repas chaleureux</li>
|
|
||||||
<li>Tequila pour le digestif</li>
|
|
||||||
<li>Voyage dans le temps en visitant quelques authentiques estaminets</li>
|
|
||||||
<li>Tequila finale pour se dire au revoir</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
|
||||||
<span class="badge-budget">
|
|
||||||
<i class="bi bi-currency-euro me-1"></i> Budget : ~75 € / pers.
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Rating Widget -->
|
|
||||||
<div class="rating-wrapper">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2 px-1">
|
|
||||||
<span class="rating-title">Votre évaluation</span>
|
|
||||||
<button type="button" class="reset-btn" onclick="resetRating('tequila')">Effacer (0★)</button>
|
|
||||||
</div>
|
|
||||||
<div class="stars-container" data-proposal="tequila">
|
|
||||||
<button type="button" class="star-btn" data-value="1"><i class="bi bi-star-fill"></i></button>
|
|
||||||
<button type="button" class="star-btn" data-value="2"><i class="bi bi-star-fill"></i></button>
|
|
||||||
<button type="button" class="star-btn" data-value="3"><i class="bi bi-star-fill"></i></button>
|
|
||||||
</div>
|
|
||||||
<input type="hidden" name="vote_tequila" id="input_tequila" value="0">
|
|
||||||
<div class="rating-feedback" id="feedback_tequila">Pas encore voté</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Proposal 4: Vivien -->
|
|
||||||
<div class="col-lg-6 col-md-12">
|
|
||||||
<div class="proposal-card">
|
|
||||||
<div class="card-number">04</div>
|
|
||||||
<div>
|
|
||||||
<h3 class="proposal-title">La journée "Vivien de La Grande Propreté"</h3>
|
|
||||||
<ul class="proposal-details">
|
|
||||||
<li>10h00 : RDV chez Bruxelles Propreté pour une présentation historique sur cette administration soeur</li>
|
|
||||||
<li>10h45 : Présentation instructive sur les statuts juridiques de Bruxelles Propreté</li>
|
|
||||||
<li>12h00 : Sandwich du jour offert</li>
|
|
||||||
<li>Accompagnement en immersion totale d'une équipe de collecte de sacs (avec tour en camion et siège passager !)</li>
|
|
||||||
<li>Inspection approfondie de sacs d'ordure pour identifier le non-respect du tri</li>
|
|
||||||
<li>Délicieux jus de pomme bio pour clôturer cette formidable journée</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-between align-items-center mt-3">
|
|
||||||
<span class="badge bg-dark border border-secondary text-secondary px-2.5 py-1.5" style="font-size:0.8rem; border-radius:8px;">
|
|
||||||
<i class="bi bi-gift me-1"></i> Budget minimal
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Rating Widget -->
|
|
||||||
<div class="rating-wrapper">
|
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2 px-1">
|
|
||||||
<span class="rating-title">Votre évaluation</span>
|
|
||||||
<button type="button" class="reset-btn" onclick="resetRating('vivien')">Effacer (0★)</button>
|
|
||||||
</div>
|
|
||||||
<div class="stars-container" data-proposal="vivien">
|
|
||||||
<button type="button" class="star-btn" data-value="1"><i class="bi bi-star-fill"></i></button>
|
|
||||||
<button type="button" class="star-btn" data-value="2"><i class="bi bi-star-fill"></i></button>
|
|
||||||
<button type="button" class="star-btn" data-value="3"><i class="bi bi-star-fill"></i></button>
|
|
||||||
</div>
|
|
||||||
<input type="hidden" name="vote_vivien" id="input_vivien" value="0">
|
|
||||||
<div class="rating-feedback" id="feedback_vivien">Pas encore voté</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Voter identity and Submission block -->
|
|
||||||
<div class="voter-identity-card">
|
|
||||||
<div class="row justify-content-center align-items-center g-4">
|
|
||||||
<div class="col-md-7 col-12 text-center text-md-start">
|
|
||||||
<h3 class="mb-2">Prêt à soumettre ?</h3>
|
|
||||||
<p class="text-muted mb-0" style="font-size:0.95rem;">
|
|
||||||
Vous pouvez indiquer votre nom ci-contre pour que l'organisateur sache qui a voté (facultatif). Le vote est anonyme par défaut.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-5 col-12">
|
|
||||||
<div class="d-flex flex-column gap-3">
|
|
||||||
<input type="text" name="voter_name" class="form-control input-voter-name" placeholder="Votre nom (facultatif)" maxlength="100">
|
|
||||||
<button type="submit" class="btn-submit-vote w-100">
|
|
||||||
<i class="bi bi-check-circle-fill me-2"></i> Soumettre mes votes
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-center mt-4 pt-3 border-top border-secondary border-opacity-25">
|
|
||||||
<a href="{% url 'home:flexi_teambuilding_results' %}" class="text-info text-decoration-none" style="font-size:0.95rem;">
|
|
||||||
<i class="bi bi-bar-chart-fill me-1"></i> Voir les résultats actuels sans voter
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Scripts for interactive stars and validation -->
|
|
||||||
<script>
|
|
||||||
const feedbacks = {
|
|
||||||
0: "Aucun intérêt (0★)",
|
|
||||||
1: "Pourquoi pas (1★)",
|
|
||||||
2: "Très bonne idée (2★)",
|
|
||||||
3: "Le rêve absolu ! (3★)"
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
|
||||||
const containers = document.querySelectorAll(".stars-container");
|
|
||||||
|
|
||||||
containers.forEach(container => {
|
|
||||||
const proposal = container.getAttribute("data-proposal");
|
|
||||||
const input = document.getElementById(`input_${proposal}`);
|
|
||||||
const feedback = document.getElementById(`feedback_${proposal}`);
|
|
||||||
const stars = container.querySelectorAll(".star-btn");
|
|
||||||
|
|
||||||
stars.forEach((star, index) => {
|
|
||||||
// Click handler
|
|
||||||
star.addEventListener("click", () => {
|
|
||||||
const val = index + 1;
|
|
||||||
input.value = val;
|
|
||||||
updateStars(stars, val);
|
|
||||||
feedback.textContent = feedbacks[val];
|
|
||||||
feedback.classList.add("rated");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Hover states
|
|
||||||
star.addEventListener("mouseenter", () => {
|
|
||||||
highlightStars(stars, index + 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
star.addEventListener("mouseleave", () => {
|
|
||||||
const currentVal = parseInt(input.value) || 0;
|
|
||||||
highlightStars(stars, currentVal);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function highlightStars(stars, val) {
|
|
||||||
stars.forEach((s, idx) => {
|
|
||||||
if (idx < val) {
|
|
||||||
s.classList.add("hover");
|
|
||||||
} else {
|
|
||||||
s.classList.remove("hover");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateStars(stars, val) {
|
|
||||||
stars.forEach((s, idx) => {
|
|
||||||
if (idx < val) {
|
|
||||||
s.classList.add("active");
|
|
||||||
} else {
|
|
||||||
s.classList.remove("active");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetRating(proposal) {
|
|
||||||
const input = document.getElementById(`input_${proposal}`);
|
|
||||||
const feedback = document.getElementById(`feedback_${proposal}`);
|
|
||||||
const container = document.querySelector(`.stars-container[data-proposal="${proposal}"]`);
|
|
||||||
const stars = container.querySelectorAll(".star-btn");
|
|
||||||
|
|
||||||
input.value = 0;
|
|
||||||
updateStars(stars, 0);
|
|
||||||
highlightStars(stars, 0);
|
|
||||||
feedback.textContent = feedbacks[0];
|
|
||||||
feedback.classList.remove("rated");
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,430 +0,0 @@
|
||||||
{% load static %}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="fr">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Flexi Teambuilding - Résultats</title>
|
|
||||||
<link rel="icon" href="{% static 'common/favicon.ico' %}" sizes="any">
|
|
||||||
|
|
||||||
<!-- Google Fonts -->
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=Plus+Jakarta+Sans:wght@300;400;500;700&display=swap" rel="stylesheet">
|
|
||||||
|
|
||||||
<!-- Bootstrap Icons (local) -->
|
|
||||||
<link href="{% static 'common/bootstrap-icons.min.css' %}" rel="stylesheet">
|
|
||||||
<!-- Bootstrap CSS (local) -->
|
|
||||||
<link href="{% static 'common/bootstrap.min.css' %}" rel="stylesheet">
|
|
||||||
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
--bg-gradient: linear-gradient(135deg, #0f172a 0%, #1e1b4b 50%, #311042 100%);
|
|
||||||
--card-bg: rgba(30, 41, 59, 0.45);
|
|
||||||
--card-border: rgba(255, 255, 255, 0.08);
|
|
||||||
--accent-color: #06b6d4;
|
|
||||||
--accent-gradient: linear-gradient(90deg, #3b82f6 0%, #06b6d4 100%);
|
|
||||||
--text-main: #f8fafc;
|
|
||||||
--text-muted: #94a3b8;
|
|
||||||
--star-color: #fbbf24;
|
|
||||||
--success-color: #10b981;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
|
||||||
background: var(--bg-gradient);
|
|
||||||
background-attachment: fixed;
|
|
||||||
color: var(--text-main);
|
|
||||||
min-height: 100vh;
|
|
||||||
padding: 3rem 0;
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2, h3, .heading-font {
|
|
||||||
font-family: 'Outfit', sans-serif;
|
|
||||||
font-weight: 800;
|
|
||||||
}
|
|
||||||
|
|
||||||
.glass-header {
|
|
||||||
background: rgba(15, 23, 42, 0.4);
|
|
||||||
backdrop-filter: blur(16px);
|
|
||||||
border-bottom: 1px solid var(--card-border);
|
|
||||||
padding: 2rem;
|
|
||||||
border-radius: 24px;
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
|
|
||||||
text-align: center;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
font-size: 2.8rem;
|
|
||||||
background: linear-gradient(120deg, #60a5fa, #38bdf8, #34d399);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-subtitle {
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success-banner {
|
|
||||||
background: rgba(16, 185, 129, 0.12);
|
|
||||||
border: 1px solid rgba(16, 185, 129, 0.3);
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 1.25rem 2rem;
|
|
||||||
margin-bottom: 2.5rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
color: #a7f3d0;
|
|
||||||
box-shadow: 0 8px 20px rgba(16, 185, 129, 0.08);
|
|
||||||
animation: slideInDown 0.5s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes slideInDown {
|
|
||||||
from {
|
|
||||||
transform: translateY(-20px);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: translateY(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-summary {
|
|
||||||
background: rgba(15, 23, 42, 0.3);
|
|
||||||
border: 1px solid var(--card-border);
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: 1.5rem 2.5rem;
|
|
||||||
margin-bottom: 2.5rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats-number {
|
|
||||||
font-size: 3.5rem;
|
|
||||||
font-weight: 800;
|
|
||||||
background: var(--accent-gradient);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.results-container {
|
|
||||||
background: var(--card-bg);
|
|
||||||
backdrop-filter: blur(12px);
|
|
||||||
border: 1px solid var(--card-border);
|
|
||||||
border-radius: 24px;
|
|
||||||
padding: 2.5rem;
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
|
||||||
margin-bottom: 3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-row {
|
|
||||||
padding: 1.75rem 0;
|
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-row:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.result-row:first-child {
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proposal-name {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #f1f5f9;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stars-avg {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.star-icon {
|
|
||||||
font-size: 1.3rem;
|
|
||||||
color: var(--star-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.star-icon.empty {
|
|
||||||
color: #475569;
|
|
||||||
}
|
|
||||||
|
|
||||||
.score-badge {
|
|
||||||
background: rgba(251, 191, 36, 0.15);
|
|
||||||
color: #fbbf24;
|
|
||||||
padding: 0.25rem 0.6rem;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
border: 1px solid rgba(251, 191, 36, 0.25);
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-progress {
|
|
||||||
background: #1e293b;
|
|
||||||
height: 14px;
|
|
||||||
border-radius: 50px;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.03);
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-progress-bar {
|
|
||||||
background: var(--accent-gradient);
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 50px;
|
|
||||||
transition: width 1s ease-out;
|
|
||||||
box-shadow: 0 0 12px rgba(6, 182, 212, 0.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.participants-card {
|
|
||||||
background: var(--card-bg);
|
|
||||||
backdrop-filter: blur(12px);
|
|
||||||
border: 1px solid var(--card-border);
|
|
||||||
border-radius: 24px;
|
|
||||||
padding: 2.5rem;
|
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
.participant-item {
|
|
||||||
background: rgba(15, 23, 42, 0.3);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.03);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 0.8rem 1.2rem;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
color: #cbd5e1;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.participant-item span.time {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-action-back {
|
|
||||||
background: transparent;
|
|
||||||
border: 1px solid var(--card-border);
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
padding: 0.75rem 1.75rem;
|
|
||||||
border-radius: 12px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
text-decoration: none;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-action-back:hover {
|
|
||||||
color: var(--text-main);
|
|
||||||
border-color: rgba(255, 255, 255, 0.25);
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-number {
|
|
||||||
position: absolute;
|
|
||||||
right: 1.5rem;
|
|
||||||
top: 1rem;
|
|
||||||
font-size: 3.5rem;
|
|
||||||
font-weight: 900;
|
|
||||||
color: rgba(255, 255, 255, 0.03);
|
|
||||||
line-height: 1;
|
|
||||||
user-select: none;
|
|
||||||
font-family: 'Outfit', sans-serif;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
<!-- Success Banner -->
|
|
||||||
{% if success %}
|
|
||||||
<div class="success-banner">
|
|
||||||
<i class="bi bi-check-circle-fill" style="font-size: 1.75rem; color: var(--success-color);"></i>
|
|
||||||
<div>
|
|
||||||
<h5 class="mb-0 fw-bold">Votre vote a bien été enregistré !</h5>
|
|
||||||
<p class="mb-0" style="font-size: 0.9rem; opacity: 0.85;">Merci pour votre participation active au teambuilding.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- Header Block -->
|
|
||||||
<div class="glass-header">
|
|
||||||
<div class="card-number" style="left:1rem; right:auto; top:0.5rem;">FTB</div>
|
|
||||||
<h1 class="header-title">Résultats des Votes</h1>
|
|
||||||
<p class="header-subtitle">Visualisation en temps réel des avis exprimés sur le Flexi Teambuilding</p>
|
|
||||||
|
|
||||||
<div class="stats-summary d-inline-flex px-4 py-2 mt-2">
|
|
||||||
<div>
|
|
||||||
<div class="stats-number">{{ total_votes }}</div>
|
|
||||||
<div class="text-uppercase tracking-wider" style="font-size:0.75rem; color: var(--text-muted); font-weight:600; margin-top:2px;">
|
|
||||||
Participation{{ total_votes|pluralize }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Results Overview -->
|
|
||||||
<div class="results-container">
|
|
||||||
<h2 class="mb-4 pb-2 border-bottom border-secondary border-opacity-25 fw-bold" style="font-size: 1.6rem;">
|
|
||||||
Moyennes des évaluations
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
{% if total_votes > 0 %}
|
|
||||||
<!-- GAEL -->
|
|
||||||
<div class="result-row">
|
|
||||||
<div class="proposal-name">1) La journée "Gaël Non Di Dju"</div>
|
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
|
||||||
<div class="stars-avg" id="stars-gael" data-score="{{ avg_gael }}">
|
|
||||||
<!-- Star icons generated dynamically by JavaScript -->
|
|
||||||
<span class="score-badge">{{ avg_gael }} ★</span>
|
|
||||||
</div>
|
|
||||||
<div class="text-muted" style="font-size: 0.85rem;">{{ pct_gael }}% d'approbation</div>
|
|
||||||
</div>
|
|
||||||
<div class="custom-progress">
|
|
||||||
<div class="custom-progress-bar" style="width: 0%;" data-width="{{ pct_gael }}%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- CHARLY -->
|
|
||||||
<div class="result-row">
|
|
||||||
<div class="proposal-name">2) La journée "La cité de Charly"</div>
|
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
|
||||||
<div class="stars-avg" id="stars-charly" data-score="{{ avg_charly }}">
|
|
||||||
<span class="score-badge">{{ avg_charly }} ★</span>
|
|
||||||
</div>
|
|
||||||
<div class="text-muted" style="font-size: 0.85rem;">{{ pct_charly }}% d'approbation</div>
|
|
||||||
</div>
|
|
||||||
<div class="custom-progress">
|
|
||||||
<div class="custom-progress-bar" style="width: 0%;" data-width="{{ pct_charly }}%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- TEQUILA -->
|
|
||||||
<div class="result-row">
|
|
||||||
<div class="proposal-name">3) La journée "Tequila Cadena"</div>
|
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
|
||||||
<div class="stars-avg" id="stars-tequila" data-score="{{ avg_tequila }}">
|
|
||||||
<span class="score-badge">{{ avg_tequila }} ★</span>
|
|
||||||
</div>
|
|
||||||
<div class="text-muted" style="font-size: 0.85rem;">{{ pct_tequila }}% d'approbation</div>
|
|
||||||
</div>
|
|
||||||
<div class="custom-progress">
|
|
||||||
<div class="custom-progress-bar" style="width: 0%;" data-width="{{ pct_tequila }}%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- VIVIEN -->
|
|
||||||
<div class="result-row">
|
|
||||||
<div class="proposal-name">4) La journée "Vivien de La Grande Propreté"</div>
|
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
|
||||||
<div class="stars-avg" id="stars-vivien" data-score="{{ avg_vivien }}">
|
|
||||||
<span class="score-badge">{{ avg_vivien }} ★</span>
|
|
||||||
</div>
|
|
||||||
<div class="text-muted" style="font-size: 0.85rem;">{{ pct_vivien }}% d'approbation</div>
|
|
||||||
</div>
|
|
||||||
<div class="custom-progress">
|
|
||||||
<div class="custom-progress-bar" style="width: 0%;" data-width="{{ pct_vivien }}%"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="text-center py-5">
|
|
||||||
<i class="bi bi-chat-left-dots text-secondary mb-3" style="font-size: 3rem; display:block;"></i>
|
|
||||||
<h4 class="text-muted">Aucun vote n'a encore été enregistré.</h4>
|
|
||||||
<p class="text-muted mb-0">Soyez le premier à donner votre avis !</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Voters / Participants List -->
|
|
||||||
{% if total_votes > 0 %}
|
|
||||||
<div class="participants-card mb-5">
|
|
||||||
<h2 class="mb-4 pb-2 border-bottom border-secondary border-opacity-25 fw-bold" style="font-size: 1.6rem;">
|
|
||||||
Votants récents
|
|
||||||
</h2>
|
|
||||||
<div class="row">
|
|
||||||
{% for vote in votes %}
|
|
||||||
<div class="col-md-6 col-12">
|
|
||||||
<div class="participant-item">
|
|
||||||
<span class="fw-medium">
|
|
||||||
<i class="bi bi-person-circle me-2 text-info"></i>{{ vote.voter_name|default:"Votant Anonyme" }}
|
|
||||||
</span>
|
|
||||||
<span class="time">Voté le {{ vote.created_at|date:"d/m/Y à H:i" }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- Actions block -->
|
|
||||||
<div class="text-center">
|
|
||||||
<a href="{% url 'home:flexi_teambuilding' %}" class="btn-action-back">
|
|
||||||
<i class="bi bi-arrow-left"></i> Retour au formulaire de vote
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Scripts for star calculation and progress bars animation -->
|
|
||||||
<script>
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
|
||||||
// Animate progress bars
|
|
||||||
setTimeout(() => {
|
|
||||||
const bars = document.querySelectorAll(".custom-progress-bar");
|
|
||||||
bars.forEach(bar => {
|
|
||||||
bar.style.width = bar.getAttribute("data-width");
|
|
||||||
});
|
|
||||||
}, 150);
|
|
||||||
|
|
||||||
// Generate stars representation for averages
|
|
||||||
const ids = ["stars-gael", "stars-charly", "stars-tequila", "stars-vivien"];
|
|
||||||
ids.forEach(id => {
|
|
||||||
const wrapper = document.getElementById(id);
|
|
||||||
if (!wrapper) return;
|
|
||||||
|
|
||||||
const score = parseFloat(wrapper.getAttribute("data-score")) || 0;
|
|
||||||
const badge = wrapper.querySelector(".score-badge");
|
|
||||||
|
|
||||||
// Clear wrapper content, keeping badge
|
|
||||||
wrapper.innerHTML = "";
|
|
||||||
|
|
||||||
// Build 3 stars
|
|
||||||
for (let i = 1; i <= 3; i++) {
|
|
||||||
const star = document.createElement("span");
|
|
||||||
if (i <= score) {
|
|
||||||
star.className = "star-icon bi bi-star-fill";
|
|
||||||
} else if (i - 0.5 <= score) {
|
|
||||||
star.className = "star-icon bi bi-star-half";
|
|
||||||
} else {
|
|
||||||
star.className = "star-icon empty bi bi-star";
|
|
||||||
}
|
|
||||||
wrapper.appendChild(star);
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper.appendChild(badge);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -4,7 +4,7 @@ from datetime import date
|
||||||
|
|
||||||
from common.models import Role, Thematic, UserConfig, UserThematics
|
from common.models import Role, Thematic, UserConfig, UserThematics
|
||||||
from contracts.models import Company, CompanyTeam, Contract
|
from contracts.models import Company, CompanyTeam, Contract
|
||||||
from home.models import HomeWidget, HomeWidgetConditionGroup, HomeWidgetCondition, TeambuildingVote
|
from home.models import HomeWidget, HomeWidgetConditionGroup, HomeWidgetCondition
|
||||||
|
|
||||||
|
|
||||||
class HomeWidgetConditionTests(TestCase):
|
class HomeWidgetConditionTests(TestCase):
|
||||||
|
|
@ -187,67 +187,6 @@ class HomeWidgetConditionTests(TestCase):
|
||||||
self.assertEqual(self.group.get_summary(), "not(role=admin)")
|
self.assertEqual(self.group.get_summary(), "not(role=admin)")
|
||||||
|
|
||||||
|
|
||||||
class TeambuildingVoteViewsTests(TestCase):
|
|
||||||
def test_flexi_teambuilding_get(self):
|
|
||||||
# Page should be publicly accessible without logging in
|
|
||||||
response = self.client.get("/flexi-teambuilding/")
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertTemplateUsed(response, "home/flexi_teambuilding.html")
|
|
||||||
|
|
||||||
def test_flexi_teambuilding_post(self):
|
|
||||||
# Submit a vote (publicly accessible)
|
|
||||||
response = self.client.post("/flexi-teambuilding/", {
|
|
||||||
"voter_name": "Jean-Luc",
|
|
||||||
"vote_gael": "3",
|
|
||||||
"vote_charly": "2",
|
|
||||||
"vote_tequila": "1",
|
|
||||||
"vote_vivien": "0"
|
|
||||||
})
|
|
||||||
# Should redirect to results page with success query param
|
|
||||||
self.assertRedirects(response, "/flexi-teambuilding/results/?success=1")
|
|
||||||
|
|
||||||
# Verify the database entry
|
|
||||||
self.assertEqual(TeambuildingVote.objects.count(), 1)
|
|
||||||
vote = TeambuildingVote.objects.first()
|
|
||||||
self.assertEqual(vote.voter_name, "Jean-Luc")
|
|
||||||
self.assertEqual(vote.vote_gael, 3)
|
|
||||||
self.assertEqual(vote.vote_charly, 2)
|
|
||||||
self.assertEqual(vote.vote_tequila, 1)
|
|
||||||
self.assertEqual(vote.vote_vivien, 0)
|
|
||||||
|
|
||||||
def test_flexi_teambuilding_results_calculation(self):
|
|
||||||
# Create dummy votes
|
|
||||||
TeambuildingVote.objects.create(
|
|
||||||
voter_name="Alice",
|
|
||||||
vote_gael=3,
|
|
||||||
vote_charly=2,
|
|
||||||
vote_tequila=1,
|
|
||||||
vote_vivien=0
|
|
||||||
)
|
|
||||||
TeambuildingVote.objects.create(
|
|
||||||
voter_name="Bob",
|
|
||||||
vote_gael=1,
|
|
||||||
vote_charly=2,
|
|
||||||
vote_tequila=3,
|
|
||||||
vote_vivien=3
|
|
||||||
)
|
|
||||||
|
|
||||||
# Access results page (publicly accessible)
|
|
||||||
response = self.client.get("/flexi-teambuilding/results/")
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
self.assertTemplateUsed(response, "home/flexi_teambuilding_results.html")
|
|
||||||
|
|
||||||
# Check context averages
|
|
||||||
self.assertEqual(response.context["total_votes"], 2)
|
|
||||||
self.assertEqual(response.context["avg_gael"], 2.0)
|
|
||||||
self.assertEqual(response.context["avg_charly"], 2.0)
|
|
||||||
self.assertEqual(response.context["avg_tequila"], 2.0)
|
|
||||||
self.assertEqual(response.context["avg_vivien"], 1.5)
|
|
||||||
|
|
||||||
self.assertEqual(response.context["pct_gael"], 66.7)
|
|
||||||
self.assertEqual(response.context["pct_charly"], 66.7)
|
|
||||||
self.assertEqual(response.context["pct_tequila"], 66.7)
|
|
||||||
self.assertEqual(response.context["pct_vivien"], 50.0)
|
|
||||||
|
|
||||||
|
|
||||||
class HomeIndexViewTests(TestCase):
|
class HomeIndexViewTests(TestCase):
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,4 @@ urlpatterns = [
|
||||||
path("", views.index, name="index"),
|
path("", views.index, name="index"),
|
||||||
path("privacy/", views.privacy, name="privacy"),
|
path("privacy/", views.privacy, name="privacy"),
|
||||||
path("api/incident-count/", views.incident_count_api, name="incident_count_api"),
|
path("api/incident-count/", views.incident_count_api, name="incident_count_api"),
|
||||||
path("flexi-teambuilding/", views.flexi_teambuilding, name="flexi_teambuilding"),
|
|
||||||
path("flexi-teambuilding/results/", views.flexi_teambuilding_results, name="flexi_teambuilding_results"),
|
|
||||||
]
|
]
|
||||||
|
|
@ -27,7 +27,7 @@ from interventions.models import Intervention, InterventionTimeLine, STATUS_ORDE
|
||||||
from common.models import UserConfig, UserThematics, UserContractAccess
|
from common.models import UserConfig, UserThematics, UserContractAccess
|
||||||
from contracts.models import CompanyMember, Contract
|
from contracts.models import CompanyMember, Contract
|
||||||
from contracts.permissions import get_allowed_contracts_for_user
|
from contracts.permissions import get_allowed_contracts_for_user
|
||||||
from home.models import HomeWidget, TeambuildingVote
|
from home.models import HomeWidget
|
||||||
from observations.models import Observation
|
from observations.models import Observation
|
||||||
from stock.models import PreparationOrder, ProductionOrder, StockAlert, PurchaseOrderItem, StockMovement, StockMovementBatch, PurchaseOrder
|
from stock.models import PreparationOrder, ProductionOrder, StockAlert, PurchaseOrderItem, StockMovement, StockMovementBatch, PurchaseOrder
|
||||||
|
|
||||||
|
|
@ -843,68 +843,4 @@ def incident_count_api(request):
|
||||||
}
|
}
|
||||||
for obs in qs[:5]
|
for obs in qs[:5]
|
||||||
]
|
]
|
||||||
return JsonResponse({'count': qs.count(), 'incidents': incidents})
|
return JsonResponse({'count': qs.count(), 'incidents': incidents})
|
||||||
|
|
||||||
|
|
||||||
@login_not_required
|
|
||||||
def flexi_teambuilding(request):
|
|
||||||
if request.method == "POST":
|
|
||||||
voter_name = request.POST.get("voter_name", "").strip() or None
|
|
||||||
vote_gael = int(request.POST.get("vote_gael", 0))
|
|
||||||
vote_charly = int(request.POST.get("vote_charly", 0))
|
|
||||||
vote_tequila = int(request.POST.get("vote_tequila", 0))
|
|
||||||
vote_vivien = int(request.POST.get("vote_vivien", 0))
|
|
||||||
|
|
||||||
# Validate rating values
|
|
||||||
vote_gael = max(0, min(3, vote_gael))
|
|
||||||
vote_charly = max(0, min(3, vote_charly))
|
|
||||||
vote_tequila = max(0, min(3, vote_tequila))
|
|
||||||
vote_vivien = max(0, min(3, vote_vivien))
|
|
||||||
|
|
||||||
TeambuildingVote.objects.create(
|
|
||||||
voter_name=voter_name,
|
|
||||||
vote_gael=vote_gael,
|
|
||||||
vote_charly=vote_charly,
|
|
||||||
vote_tequila=vote_tequila,
|
|
||||||
vote_vivien=vote_vivien
|
|
||||||
)
|
|
||||||
return redirect(reverse("home:flexi_teambuilding_results") + "?success=1")
|
|
||||||
|
|
||||||
return render(request, "home/flexi_teambuilding.html")
|
|
||||||
|
|
||||||
|
|
||||||
@login_not_required
|
|
||||||
def flexi_teambuilding_results(request):
|
|
||||||
from django.db.models import Avg
|
|
||||||
|
|
||||||
votes = TeambuildingVote.objects.all().order_by("-created_at")
|
|
||||||
total_votes = votes.count()
|
|
||||||
|
|
||||||
averages = votes.aggregate(
|
|
||||||
avg_gael=Avg("vote_gael"),
|
|
||||||
avg_charly=Avg("vote_charly"),
|
|
||||||
avg_tequila=Avg("vote_tequila"),
|
|
||||||
avg_vivien=Avg("vote_vivien")
|
|
||||||
)
|
|
||||||
|
|
||||||
avg_gael = round(averages["avg_gael"] or 0, 2)
|
|
||||||
avg_charly = round(averages["avg_charly"] or 0, 2)
|
|
||||||
avg_tequila = round(averages["avg_tequila"] or 0, 2)
|
|
||||||
avg_vivien = round(averages["avg_vivien"] or 0, 2)
|
|
||||||
|
|
||||||
success = request.GET.get("success") == "1"
|
|
||||||
|
|
||||||
context = {
|
|
||||||
"votes": votes,
|
|
||||||
"total_votes": total_votes,
|
|
||||||
"avg_gael": avg_gael,
|
|
||||||
"avg_charly": avg_charly,
|
|
||||||
"avg_tequila": avg_tequila,
|
|
||||||
"avg_vivien": avg_vivien,
|
|
||||||
"pct_gael": round((avg_gael / 3) * 100, 1) if avg_gael else 0,
|
|
||||||
"pct_charly": round((avg_charly / 3) * 100, 1) if avg_charly else 0,
|
|
||||||
"pct_tequila": round((avg_tequila / 3) * 100, 1) if avg_tequila else 0,
|
|
||||||
"pct_vivien": round((avg_vivien / 3) * 100, 1) if avg_vivien else 0,
|
|
||||||
"success": success
|
|
||||||
}
|
|
||||||
return render(request, "home/flexi_teambuilding_results.html", context)
|
|
||||||
Loading…
Reference in a new issue