Initial commit: JuanitoSport - Muay Thai & Yog4Fight PWA

This commit is contained in:
Wycz
2026-09-08 08:25:33 +02:00
commit a69ca86330
70 changed files with 4301 additions and 0 deletions
+137
View File
@@ -0,0 +1,137 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
export default function AdminHomework({ homework, members }: any) {
const router = useRouter();
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [videoFile, setVideoFile] = useState<File | null>(null);
const [videoUrl, setVideoUrl] = useState("");
const [target, setTarget] = useState("all");
const [saving, setSaving] = useState(false);
const [error, setError] = useState("");
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!title || !description) return;
setSaving(true);
setError("");
try {
let finalVideoUrl = videoUrl;
if (videoFile) {
const formData = new FormData();
formData.append("file", videoFile);
formData.append("subdir", "homework");
const up = await fetch("/api/upload", { method: "POST", body: formData });
if (!up.ok) throw new Error("Upload vidéo échoué");
const { url } = await up.json();
finalVideoUrl = url;
}
const res = await fetch("/api/homework", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title,
description,
videoUrl: finalVideoUrl,
userId: target === "all" ? null : target,
}),
});
if (!res.ok) throw new Error("Enregistrement échoué");
setTitle("");
setDescription("");
setVideoFile(null);
setVideoUrl("");
setTarget("all");
router.refresh();
} catch (err: any) {
setError(err.message || "Erreur");
} finally {
setSaving(false);
}
}
async function deleteHomework(id: string) {
if (!confirm("Supprimer cet exercice ?")) return;
await fetch("/api/homework", {
method: "DELETE",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id }),
});
router.refresh();
}
return (
<div className="space-y-4">
<h2 className="text-lg font-bold">🏋️ Exercices à travailler</h2>
<form onSubmit={handleSubmit} className="card space-y-3 border-primary-500/30">
<h3 className="font-semibold">📝 Nouvel exercice</h3>
<div>
<label className="label">Titre</label>
<input className="input" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Ex: 10 rounds de shadow box" required />
</div>
<div>
<label className="label">Description</label>
<textarea className="input min-h-[100px]" value={description} onChange={(e) => setDescription(e.target.value)} placeholder="Décris l'exercice..." required />
</div>
<div>
<label className="label">À destination de</label>
<select className="input" value={target} onChange={(e) => setTarget(e.target.value)}>
<option value="all">Tous les membres</option>
{members.map((m: any) => (
<option key={m.id} value={m.id}>{m.name}</option>
))}
</select>
</div>
<div>
<label className="label">Vidéo démonstrative (optionnel)</label>
{videoFile ? (
<p className="mb-2 text-sm text-primary-300">{videoFile.name}</p>
) : (
<label className="btn-secondary mb-2 w-full cursor-pointer">
📹 Téléverser une vidéo
<input type="file" accept="video/*" className="hidden" onChange={(e) => setVideoFile(e.target.files?.[0] || null)} />
</label>
)}
<input className="input" value={videoUrl} onChange={(e) => setVideoUrl(e.target.value)} placeholder="...ou coller une URL de vidéo" />
</div>
{error && <p className="text-sm text-red-400">{error}</p>}
<button type="submit" className="btn-primary w-full" disabled={saving}>
{saving ? "Enregistrement..." : "✅ Publier"}
</button>
</form>
<div className="space-y-3">
{homework.length === 0 && (
<p className="card text-center text-sm text-white/50">Aucun exercice publié.</p>
)}
{homework.map((h: any) => (
<div key={h.id} className="card">
<div className="flex items-start justify-between">
<div className="flex-1">
<h3 className="font-semibold">{h.title}</h3>
<p className="mt-1 whitespace-pre-line text-sm text-white/75">{h.description}</p>
<p className="mt-2 text-xs text-white/40">
{h.user ? `Pour ${h.user.name}` : "Pour tous les membres"} ·{" "}
{new Date(h.createdAt).toLocaleDateString("fr-FR")}
</p>
{h.videoUrl && (
<video controls playsInline preload="metadata" className="mt-2 aspect-video w-full rounded-xl max-w-sm" src={h.videoUrl} />
)}
</div>
<button onClick={() => deleteHomework(h.id)} className="ml-3 shrink-0 text-sm text-red-400">
🗑️
</button>
</div>
</div>
))}
</div>
</div>
);
}
+18
View File
@@ -0,0 +1,18 @@
import { prisma } from "@/lib/prisma";
import AdminHomework from "./homework-admin";
export default async function HomeworkPage() {
const [homework, members] = await Promise.all([
prisma.homework.findMany({
orderBy: { createdAt: "desc" },
include: { user: { select: { name: true } } },
}),
prisma.user.findMany({
where: { role: "MEMBER" },
select: { id: true, name: true, isMuayThai: true },
orderBy: { name: "asc" },
}),
]);
return <AdminHomework homework={homework} members={members} />;
}