feat: edition complete d'une activite existante

This commit is contained in:
2026-11-13 12:36:54 +01:00
parent 9c48217c36
commit 174fc6872e
2 changed files with 215 additions and 70 deletions

View File

@@ -270,13 +270,17 @@ app.patch('/api/activities/:id', (req, res) => {
const emoji = req.body.emoji ?? existing.emoji;
const image_url = req.body.image_url !== undefined ? req.body.image_url : existing.image_url;
const cost_shells = req.body.cost_shells !== undefined ? Number(req.body.cost_shells) : existing.cost_shells;
const status = req.body.status ?? existing.status;
const current_shells = req.body.current_shells !== undefined ? Math.max(0, Number(req.body.current_shells)) : existing.current_shells;
let status = req.body.status ?? existing.status;
if (current_shells >= cost_shells && status === 'ACTIVE') {
status = 'UNLOCKED';
}
db.prepare(`
UPDATE activities
SET title = ?, description = ?, emoji = ?, image_url = ?, cost_shells = ?, status = ?
SET title = ?, description = ?, emoji = ?, image_url = ?, cost_shells = ?, current_shells = ?, status = ?
WHERE id = ?
`).run(title, description, emoji, image_url, cost_shells, status, id);
`).run(title, description, emoji, image_url, cost_shells, current_shells, status, id);
res.json({ message: 'Activité mise à jour.' });
});

View File

@@ -805,10 +805,24 @@ export default function App() {
)}
</div>
<div className="absolute top-3 right-3 px-3.5 py-1.5 rounded-2xl bg-slate-950/85 backdrop-blur-md border border-amber-400/40 text-right">
<div className="absolute top-3 right-3 flex items-center gap-2">
<button
type="button"
onClick={(e) => {
e.stopPropagation();
setEditingActivity(act);
}}
className="px-3 py-1.5 rounded-2xl bg-slate-950/85 hover:bg-slate-900 border border-white/20 hover:border-amber-400 text-slate-300 hover:text-amber-300 text-xs font-bold backdrop-blur-md transition cursor-pointer flex items-center gap-1.5 shadow-md"
title="Modifier cette activité (titre, coût, description, photo...)"
>
<Settings className="w-3.5 h-3.5" /> Modifier
</button>
<div className="px-3.5 py-1.5 rounded-2xl bg-slate-950/85 backdrop-blur-md border border-amber-400/40 text-right">
<span className="text-[10px] text-slate-300 block leading-none">Coût</span>
<span className="text-lg font-black text-amber-300">{act.cost_shells} 🐚</span>
</div>
</div>
<div className="absolute bottom-3 left-4 right-4 flex items-end justify-between">
<div className="flex items-center gap-2.5">
@@ -1241,10 +1255,10 @@ export default function App() {
</div>
</div>
{/* Liste des activités existantes avec possibilité de changer leur photo */}
{/* Liste des activités existantes avec possibilité de les modifier */}
<div className="mt-8 pt-6 border-t border-white/10">
<h3 className="text-sm font-bold text-slate-300 mb-3">
Catalogue actuel (cliquez sur « Modifier l'image » pour changer l'illustration d'une activité existante) :
Catalogue actuel (cliquez sur « Modifier l'activité » pour éditer son nom, coût, emoji, description ou photo) :
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3">
{activities.map((a) => (
@@ -1262,12 +1276,16 @@ export default function App() {
<div className="p-2.5 flex items-center justify-between gap-2">
<button
onClick={() => setEditingActivity(a)}
className="flex-1 py-1.5 px-2.5 rounded-lg bg-cyan-500/20 hover:bg-cyan-500/30 text-cyan-200 text-xs font-semibold cursor-pointer transition flex items-center justify-center gap-1"
className="flex-1 py-1.5 px-2.5 rounded-lg bg-amber-400/15 hover:bg-amber-400/25 border border-amber-400/30 text-amber-300 text-xs font-semibold cursor-pointer transition flex items-center justify-center gap-1.5"
>
<ImageIcon className="w-3.5 h-3.5" /> Modifier l'image
<Settings className="w-3.5 h-3.5" /> Modifier l'activité
</button>
<button
onClick={() => handleDeleteActivity(a.id)}
onClick={() => {
if (confirm(`Supprimer l'activité "${a.title}" ?`)) {
handleDeleteActivity(a.id);
}
}}
className="p-1.5 rounded-lg bg-rose-500/20 hover:bg-rose-500/30 text-rose-300 cursor-pointer"
title="Supprimer l'activité"
>
@@ -1784,47 +1802,143 @@ export default function App() {
)}
</main>
{/* Modal for Editing an Existing Activity's Illustration */}
{/* Modal Complète pour Modifier une Activité Existante (Titre, Coût, Cagnotte, Statut, Photo) */}
{editingActivity && (
<div className="fixed inset-0 z-50 bg-black/75 backdrop-blur-sm flex items-center justify-center p-4">
<div className="bg-slate-900 border border-white/15 rounded-3xl max-w-lg w-full p-6 space-y-4 shadow-2xl">
<div className="flex items-center justify-between">
<h3 className="text-lg font-extrabold text-white">
🖼️ Modifier l'illustration Alexa : {editingActivity.title}
<div className="fixed inset-0 z-50 bg-black/80 backdrop-blur-md flex items-center justify-center p-4 overflow-y-auto">
<div className="bg-slate-900 border border-white/15 rounded-3xl max-w-xl w-full p-6 space-y-5 shadow-2xl my-8">
<div className="flex items-center justify-between border-b border-white/10 pb-4">
<div>
<span className="text-xs text-amber-300 font-extrabold uppercase tracking-wider block">
⚙️ Modification de l'activité
</span>
<h3 className="text-xl font-extrabold text-white flex items-center gap-2 mt-0.5">
<span>{editingActivity.emoji}</span> {editingActivity.title}
</h3>
</div>
<button
onClick={() => setEditingActivity(null)}
className="text-slate-400 hover:text-white text-sm cursor-pointer"
className="text-slate-400 hover:text-white text-sm cursor-pointer p-1"
>
Fermer ✕
✕
</button>
</div>
<form onSubmit={handleSaveEditedActivity} className="space-y-4">
<div className="relative h-44 rounded-2xl overflow-hidden border border-white/15">
{/* 1. Titre & Emoji & Coût */}
<div className="grid grid-cols-1 sm:grid-cols-12 gap-3">
<div className="sm:col-span-2">
<label className="block text-xs font-bold text-slate-300 mb-1">Emoji</label>
<input
type="text"
value={editingActivity.emoji}
onChange={(e) => setEditingActivity({ ...editingActivity, emoji: e.target.value })}
className="w-full bg-slate-950 border border-white/15 rounded-xl px-2.5 py-2 text-center text-xl font-bold"
/>
</div>
<div className="sm:col-span-6">
<label className="block text-xs font-bold text-slate-300 mb-1">
Nom de l'activité (reconnu par Alexa)
</label>
<input
type="text"
required
value={editingActivity.title}
onChange={(e) => setEditingActivity({ ...editingActivity, title: e.target.value })}
className="w-full bg-slate-950 border border-white/15 rounded-xl px-3.5 py-2 text-sm text-white font-semibold"
/>
</div>
<div className="sm:col-span-4">
<label className="block text-xs font-bold text-slate-300 mb-1">Coût (en 🐚)</label>
<input
type="number"
min="1"
required
value={editingActivity.cost_shells}
onChange={(e) => setEditingActivity({ ...editingActivity, cost_shells: Number(e.target.value) })}
className="w-full bg-slate-950 border border-white/15 rounded-xl px-3.5 py-2 text-sm font-extrabold text-amber-300 text-center"
/>
</div>
</div>
{/* 2. Cagnotte actuelle & Statut */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<div>
<label className="block text-xs font-bold text-slate-300 mb-1">
Cagnotte réunie (coquillages déjà mis)
</label>
<input
type="number"
min="0"
value={editingActivity.current_shells}
onChange={(e) => setEditingActivity({ ...editingActivity, current_shells: Number(e.target.value) })}
className="w-full bg-slate-950 border border-white/15 rounded-xl px-3.5 py-2 text-sm font-extrabold text-emerald-400"
/>
</div>
<div>
<label className="block text-xs font-bold text-slate-300 mb-1">Statut de l'activité</label>
<select
value={editingActivity.status}
onChange={(e) => setEditingActivity({ ...editingActivity, status: e.target.value })}
className="w-full bg-slate-950 border border-white/15 rounded-xl px-3.5 py-2 text-xs font-bold text-slate-200"
>
<option value="ACTIVE">⏳ En cours (ACTIVE)</option>
<option value="UNLOCKED">🎉 Débloquée ! (UNLOCKED)</option>
<option value="COMPLETED">✅ Terminée / Faite (COMPLETED)</option>
</select>
</div>
</div>
{/* 3. Description */}
<div>
<label className="block text-xs font-bold text-slate-300 mb-1">
Description (lue par Alexa et affichée sur l'écran)
</label>
<textarea
rows="2"
value={editingActivity.description || ''}
onChange={(e) => setEditingActivity({ ...editingActivity, description: e.target.value })}
className="w-full bg-slate-950 border border-white/15 rounded-xl px-3.5 py-2 text-xs text-slate-200 resize-none"
/>
</div>
{/* 4. Illustration Alexa & Écran Echo Show */}
<div className="bg-slate-950/70 border border-white/10 rounded-2xl p-3.5 space-y-3">
<div className="flex items-center justify-between">
<label className="text-xs font-bold text-amber-300 uppercase tracking-wider block">
🖼️ Illustration Alexa & Écran Echo Show
</label>
</div>
{/* Aperçu de l'image sélectionnée */}
<div className="relative h-32 rounded-xl overflow-hidden border border-white/15 bg-slate-950">
<img
src={editingActivity.image_url}
alt={editingActivity.title}
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-t from-slate-950 via-transparent to-transparent flex items-end p-4">
<span className="font-bold text-white">
<div className="absolute inset-0 bg-gradient-to-t from-slate-950 via-transparent to-transparent flex items-end p-3">
<span className="font-extrabold text-xs text-white drop-shadow">
{editingActivity.emoji} {editingActivity.title} ({editingActivity.cost_shells} 🐚)
</span>
</div>
</div>
{/* Galerie 1-clic */}
<div>
<span className="text-xs text-slate-400 block mb-1.5">Choisir une image dans la galerie :</span>
<div className="grid grid-cols-4 gap-2">
<span className="text-[11px] text-slate-400 block mb-1">Choisir dans la galerie rapide :</span>
<div className="grid grid-cols-4 sm:grid-cols-8 gap-1.5">
{PRESET_GALLERY.map((p) => (
<button
type="button"
key={p.label}
onClick={() => setEditingActivity({ ...editingActivity, image_url: p.url })}
className={`h-12 rounded-lg overflow-hidden border cursor-pointer ${
editingActivity.image_url === p.url ? 'border-amber-400 ring-2 ring-amber-400' : 'border-white/10'
className={`h-11 rounded-lg overflow-hidden border cursor-pointer ${
editingActivity.image_url === p.url ? 'border-amber-400 ring-2 ring-amber-400' : 'border-white/10 opacity-70 hover:opacity-100'
}`}
title={p.label}
>
<img src={p.url} alt={p.label} className="w-full h-full object-cover" />
</button>
@@ -1832,9 +1946,10 @@ export default function App() {
</div>
</div>
{/* Upload et URL */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
<label className="py-2 px-3 rounded-xl bg-cyan-500/20 hover:bg-cyan-500/30 border border-cyan-400/40 text-cyan-200 text-xs font-bold flex items-center justify-center gap-1.5 cursor-pointer">
<Upload className="w-4 h-4" /> Envoyer une photo...
<Upload className="w-4 h-4" /> Envoyer une nouvelle photo...
<input
type="file"
accept="image/*"
@@ -1846,17 +1961,43 @@ export default function App() {
type="url"
value={editingActivity.image_url}
onChange={(e) => setEditingActivity({ ...editingActivity, image_url: e.target.value })}
placeholder="URL de l'image"
className="bg-slate-950 border border-white/15 rounded-xl px-3 py-2 text-xs"
placeholder="Ou coller une URL d'image HTTPS..."
className="bg-slate-900 border border-white/15 rounded-xl px-3 py-2 text-xs"
/>
</div>
</div>
{/* Boutons d'action */}
<div className="pt-2 flex items-center justify-between gap-3">
<button
type="button"
onClick={() => {
if (confirm(`Êtes-vous sûr de vouloir supprimer l'activité "${editingActivity.title}" ?`)) {
handleDeleteActivity(editingActivity.id);
setEditingActivity(null);
}
}}
className="px-3.5 py-2.5 rounded-xl bg-rose-500/20 hover:bg-rose-500/30 border border-rose-500/40 text-rose-300 text-xs font-bold flex items-center gap-1.5 cursor-pointer transition"
>
<Trash2 className="w-4 h-4" /> Supprimer l'activité
</button>
<div className="flex items-center gap-2">
<button
type="button"
onClick={() => setEditingActivity(null)}
className="px-4 py-2.5 rounded-xl bg-white/5 hover:bg-white/10 text-slate-300 text-xs font-semibold cursor-pointer"
>
Annuler
</button>
<button
type="submit"
className="w-full py-2.5 rounded-xl bg-amber-400 hover:bg-amber-300 text-slate-950 font-extrabold text-sm cursor-pointer"
className="px-6 py-2.5 rounded-xl bg-gradient-to-r from-amber-400 to-orange-500 hover:from-amber-300 hover:to-orange-400 text-slate-950 text-xs font-black transition cursor-pointer shadow-lg shadow-amber-400/20"
>
Enregistrer la nouvelle illustration
Enregistrer les modifications
</button>
</div>
</div>
</form>
</div>
</div>