feat: edition complete d'une activite existante
This commit is contained in:
@@ -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.' });
|
||||
});
|
||||
|
||||
@@ -805,9 +805,23 @@ 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">
|
||||
<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 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">
|
||||
@@ -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,79 +1802,202 @@ 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}
|
||||
</h3>
|
||||
<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">
|
||||
<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">
|
||||
{editingActivity.emoji} {editingActivity.title} ({editingActivity.cost_shells} 🐚)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
{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'
|
||||
}`}
|
||||
>
|
||||
<img src={p.url} alt={p.label} className="w-full h-full object-cover" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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...
|
||||
{/* 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="file"
|
||||
accept="image/*"
|
||||
onChange={(e) => handleFileUpload(e, true)}
|
||||
className="hidden"
|
||||
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>
|
||||
<input
|
||||
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"
|
||||
<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>
|
||||
|
||||
<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"
|
||||
>
|
||||
Enregistrer la nouvelle illustration
|
||||
</button>
|
||||
{/* 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-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-[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-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>
|
||||
))}
|
||||
</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 nouvelle photo...
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
onChange={(e) => handleFileUpload(e, true)}
|
||||
className="hidden"
|
||||
/>
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
value={editingActivity.image_url}
|
||||
onChange={(e) => setEditingActivity({ ...editingActivity, image_url: e.target.value })}
|
||||
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="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 les modifications
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user