Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
90e4f8b
Add goal list
rockynox Jun 16, 2026
41148cd
Add goal list
rockynox Jun 16, 2026
7fe4c00
Fix goals visualization: restore initGoalsManager and fix event.goal …
rockynox Jun 16, 2026
2f4bebf
Add goal list
rockynox Jun 16, 2026
4a2e7ee
Rebuild Vite assets after admin.js goals manager fix
rockynox Jun 16, 2026
d72bc08
Auto-save goals on add/remove, remove save button
rockynox Jun 16, 2026
94f2da3
Add fireworks animation when goal is reached on card widget
rockynox Jun 16, 2026
15daf50
Fix goal banner: center on card, increase display duration to 6s
rockynox Jun 16, 2026
037b326
Add goal-reached animation on donation bar
rockynox Jun 16, 2026
06fbd3a
Handle last goal reached: no false reset, correct banner message
rockynox Jun 16, 2026
7d3b3a7
Add documentation for the goals system
rockynox Jun 16, 2026
0a14e9f
Add info encart explaining goals system on stream/event edit pages
rockynox Jun 16, 2026
530bf9b
Reorder widgets accordion: card first, all sections collapsed by default
rockynox Jun 16, 2026
dace8eb
Fix accordion reorder: restore truncated card widget form in stream/e…
rockynox Jun 16, 2026
1836dad
Fix stream edit: remove orphaned card widget content left after accor…
rockynox Jun 16, 2026
831e375
Add reload reminder on test mode toggle
rockynox Jun 17, 2026
8e5ad9f
Refactor test mode: slider in header, section only when active
rockynox Jun 17, 2026
290129a
Fix test mode toggle: use hidden input so uncheck sends value
rockynox Jun 17, 2026
f6e71f3
Fix donation bar counter stuck after last goal reached
rockynox Jun 17, 2026
e2ddc26
Remove branch deploy
rockynox Jun 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/php-sandbox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Build & deploy sandbox

on:
push:
branches: [ "develop", "add-crowd-compatibility" ]
branches: [ "develop" ]
workflow_dispatch:

permissions:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ log.txt

node_modules/
/.phpunit.result.cache
/.tokensave/
182 changes: 182 additions & 0 deletions docs/goals-system.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# Système d'objectifs de collecte

## Vue d'ensemble

Les streams et les événements peuvent définir une liste ordonnée d'objectifs (montants en €). La barre de don et le widget carte avancent vers l'objectif actif ; une fois atteint, l'objectif suivant devient actif automatiquement, avec une animation de célébration.

---

## Base de données

### Table `{prefix}goals`

```sql
CREATE TABLE {prefix}goals (
id INT AUTO_INCREMENT PRIMARY KEY,
charity_stream_guid VARCHAR(255) NULL,
charity_event_guid VARCHAR(255) NULL,
amount INT NOT NULL, -- montant en euros (pas en centimes)
INDEX idx_stream_guid (charity_stream_guid),
INDEX idx_event_guid (charity_event_guid)
);
```

- Un objectif appartient soit à un stream, soit à un événement (les deux colonnes GUID sont mutuellement exclusives).
- Les objectifs d'un stream et d'un événement sont **indépendants** : un événement a ses propres objectifs, et chacun de ses streams a les siens.
- `amount` est en **euros** (pas en centimes), contrairement aux montants de dons qui sont en centimes dans le reste du code.

---

## Backend

### `GoalRepository`

| Méthode | Description |
|---|---|
| `selectAmountsByStreamGuid(string $guid): int[]` | Retourne les montants triés ASC pour un stream |
| `selectAmountsByEventGuid(string $guid): int[]` | Retourne les montants triés ASC pour un événement |
| `replaceForStream(string $guid, int[] $amounts): void` | Supprime + recrée tous les objectifs d'un stream (transactionnel) |
| `replaceForEvent(string $guid, int[] $amounts): void` | Idem pour un événement |
| `deleteByStreamGuid(string $guid): void` | Suppression en cascade lors de la suppression d'un stream |
| `deleteByEventGuid(string $guid): void` | Suppression en cascade lors de la suppression d'un événement |

### `resolveActiveGoal(array $goals, int $amountInCents): int`

Méthode privée de `WidgetController`. Détermine l'objectif actif à afficher.

```
goals = [500, 1000, 2000] amountInCents = 75000 (= 750 €)
→ retourne 1000 (premier objectif > 750 €)

goals = [500, 1000, 2000] amountInCents = 210000 (= 2100 €)
→ retourne 2000 (tous dépassés → dernier objectif)

goals = []
→ retourne 1 (valeur par défaut si aucun objectif défini)
```

**Comportement quand tous les objectifs sont atteints :** la barre reste à 100 % (le diviseur ne change plus). Le flag `allGoalsReached` est calculé séparément.

---

## Endpoints de fetch (polling)

Tous les 10 secondes, les widgets appellent leur endpoint de fetch. Depuis l'ajout des objectifs multiples, la réponse inclut :

| Champ | Type | Description |
|---|---|---|
| `amount` | int | Montant collecté **en centimes** |
| `goal` | int | Objectif actif **en euros** (résolu par `resolveActiveGoal`) |
| `allGoalsReached` | bool | `true` si le montant collecté ≥ dernier objectif |
| `donors` | int | Nombre de donateurs (card uniquement) |

| Endpoint | Fichier JS consommateur |
|---|---|
| `GET /widget-stream-donation/{id}/fetch` | `stream.js` |
| `GET /widget-event/{id}/fetch` | `event.js` |
| `GET /widget-stream-card/{id}/fetch` | `cardStream.js` |
| `GET /widget-event-card/{id}/fetch` | `cardEvent.js` |

---

## Interface d'administration

### Chips UI (stream et événement)

- Les objectifs s'affichent sous forme de chips triés par ordre croissant.
- **Ajouter** : saisir un montant dans le champ puis cliquer "+ Ajouter" ou appuyer sur Entrée.
- **Supprimer** : cliquer le × sur le chip.
- **Enregistrement automatique** : chaque ajout ou suppression soumet immédiatement le formulaire (pas de bouton "Enregistrer"). Le formulaire POST contient `save_goals=1` et un tableau `goal_amounts[]`.
- Les objectifs sont toujours maintenus triés ASC côté JS avant l'envoi.

### Contraintes

- Au moins un objectif est obligatoire.
- Les objectifs d'un stream et de son événement parent sont indépendants — modifier l'un ne touche pas l'autre.

---

## Widgets

### Barre de don (`donation.html.twig`)

```
window.goalAmount → objectif actif en euros (initialisé depuis Twig)
window.currentAmount → montant collecté en centimes (mis à jour par polling)
```

Le remplissage de la barre est calculé dans `utilities.js` :

```js
const percentage = Math.min(100, (currentAmountUnit / window.goalAmount) * 100);
// clip-path CSS appliqué sur div.front
```

### Widget carte (`card.html.twig`)

Même logique, barre de progression HTML classique (largeur en %) à la place du clip-path.

---

## Détection du franchissement d'un objectif

Dans `stream.js` / `event.js` / `cardStream.js` / `cardEvent.js`, la condition est évaluée **avant** de mettre à jour `window.currentAmount` :

```js
const justCrossedGoal =
json.goal !== window.goalAmount // objectif intermédiaire atteint
|| (json.allGoalsReached // OU dernier objectif atteint
&& window.currentAmount / 100 < window.goalAmount);
```

Le second cas est nécessaire car `resolveActiveGoal` retourne toujours la même valeur (le dernier objectif) quand tous sont dépassés — `json.goal` ne change donc plus, et la première condition serait `false` sans lui.

---

## Animations

### Barre de don — `triggerGoalAnimation(newGoal, counterback, counterfront, isLastGoal)`

| Phase | Durée | Ce qui se passe |
|---|---|---|
| 1 | 0 → 1 s | Barre se remplit jusqu'à 100 % via la transition CSS existante |
| 2 | 1,2 → 3,7 s | Shimmer (reflet blanc qui balaie la barre 3×) + glow pulsant (`front--celebrating`) |
| 3 | 3,7 s | Si `isLastGoal` : reste à 100 %. Sinon : `window.goalAmount` → `newGoal`, textes mis à jour, barre redescend via transition CSS |

Le flag `isAnimating` empêche deux animations simultanées.

### Widget carte — `triggerGoalCelebration(newGoal, isLastGoal)`

| Élément | Comportement |
|---|---|
| 5 explosions de particules | Positions aléatoires, couleurs variées, durée ~1,4 s avec décalage progressif |
| Banner central | Apparaît au centre de la card, animation bounce-in → 5 s de présence → fade-out |
| Texte du goal | Flash + mise à jour vers `newGoal` (ignoré si `isLastGoal`) |

**Texte du banner :**
- Objectif intermédiaire : `🎯 Objectif atteint ! Prochain objectif : X €`
- Dernier objectif : `🏆 Tous les objectifs atteints !`

---

## Cycle de vie complet

```
Admin ajoute [500, 1000, 2000] sur un stream
Widget se charge → resolveActiveGoal([500,1000,2000], 0) = 500
Don reçu → montant passe à 550 €
Polling (10 s) → goal=1000, allGoalsReached=false
justCrossedGoal: 1000 ≠ 500 → true
→ triggerGoalAnimation(1000, ..., false)
→ barre remplit à 100 %, shimmer, reset à 55 % (550/1000)
Dons → montant passe à 2100 €
Polling → goal=2000, allGoalsReached=true
justCrossedGoal: allGoalsReached && 2100/100 >= 2000 → true
→ triggerGoalAnimation(2000, ..., true)
→ barre remplit à 100 %, shimmer, reste à 100 %
```
2 changes: 2 additions & 0 deletions migrations/16-drop-donation-widget-goal.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE {prefix}widget_donation_goal_bar DROP COLUMN goal;
ALTER TABLE {prefix}widget_card DROP COLUMN goal;
17 changes: 17 additions & 0 deletions migrations/17-add-goals-table.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
CREATE TABLE {prefix}goals (
id INT AUTO_INCREMENT PRIMARY KEY,
charity_stream_guid VARCHAR(255) NULL,
charity_event_guid VARCHAR(255) NULL,
amount INT NOT NULL,
INDEX idx_stream_guid (charity_stream_guid),
INDEX idx_event_guid (charity_event_guid)
);

INSERT INTO {prefix}goals (charity_stream_guid, amount)
SELECT guid, goal FROM {prefix}charity_stream WHERE goal > 0;

INSERT INTO {prefix}goals (charity_event_guid, amount)
SELECT guid, goal FROM {prefix}charity_event WHERE goal > 0;

ALTER TABLE {prefix}charity_stream DROP COLUMN goal;
ALTER TABLE {prefix}charity_event DROP COLUMN goal;
4 changes: 4 additions & 0 deletions migrations/18-event-multi-admin.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ALTER TABLE {prefix}user_right ADD COLUMN is_owner TINYINT(1) NOT NULL DEFAULT 0;

-- All existing event admins were sole owners
UPDATE {prefix}user_right SET is_owner = 1 WHERE id_charity_event IS NOT NULL;
8 changes: 5 additions & 3 deletions public/dist/.vite/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"src": "src/Assets/js/app.js",
"isEntry": true,
"css": [
"assets/app-MiSapeh1.css"
"assets/app-B5Hc9Cjf.css"
]
},
"src/Assets/js/cardEvent.js": {
Expand All @@ -33,7 +33,8 @@
"src": "src/Assets/js/cardEvent.js",
"isEntry": true,
"imports": [
"_countUp.min.bkv6uBOp.min.js"
"_countUp.min.bkv6uBOp.min.js",
"src/Assets/js/cardUtilities.js"
]
},
"src/Assets/js/cardStream.js": {
Expand All @@ -42,7 +43,8 @@
"src": "src/Assets/js/cardStream.js",
"isEntry": true,
"imports": [
"_countUp.min.bkv6uBOp.min.js"
"_countUp.min.bkv6uBOp.min.js",
"src/Assets/js/cardUtilities.js"
]
},
"src/Assets/js/cardUtilities.js": {
Expand Down
2 changes: 1 addition & 1 deletion public/dist/admin.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion public/dist/cardEvent.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading