Files
juanitosport/app/member/muay-thai/dashboard.tsx
T

399 lines
14 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import Link from "next/link";
import Header from "@/components/Header";
import { useRouter } from "next/navigation";
import { signOut } from "next-auth/react";
type DashboardProps = {
user: any;
messages: any[];
unreadCount: number;
};
function formatDuration(seconds: number | null) {
if (!seconds) return "—";
const m = Math.floor(seconds / 60);
const s = seconds % 60;
if (m === 0) return `${s}s`;
return `${m}m${s.toString().padStart(2, "0")}`;
}
export default function MuayThaiDashboard({
user,
messages,
unreadCount,
}: DashboardProps) {
const router = useRouter();
const [media, setMedia] = useState<any[]>([]);
const [showMedia, setShowMedia] = useState(false);
const [showHomework, setShowHomework] = useState(false);
const [showMyPerf, setShowMyPerf] = useState(false);
const profile = user?.muayThai;
useEffect(() => {
fetch("/api/media")
.then((r) => r.json())
.then((data) =>
setMedia(
(data || []).filter(
(m: any) =>
m.visibility === "PUBLIC" ||
m.visibility === "MEMBERS_ONLY" ||
m.visibility === "MUAY_THAI_ONLY"
)
)
)
.catch(() => {});
}, []);
async function markAsRead(messageId: string) {
try {
await fetch("/api/messages", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messageId }),
});
router.refresh();
} catch {}
}
return (
<main className="min-h-screen pb-16">
<Header />
<div className="mx-auto max-w-3xl px-4 py-6">
<div className="mb-6 flex items-start justify-between">
<div>
<h1 className="text-2xl font-extrabold">
👋 Salut, {user.name.split(" ")[0]} !
</h1>
<p className="mt-1 text-sm text-white/60">
Voici ton espace Muay Thai
</p>
</div>
<button
onClick={() => signOut({ callbackUrl: "/" })}
className="rounded-xl border border-white/10 bg-white/5 p-2 text-sm text-white/60"
title="Déconnexion"
>
🚪
</button>
</div>
{/* Messages de Juanito */}
<section className="mb-6">
<div className="mb-3 flex items-center justify-between">
<h2 className="text-lg font-bold">
💬 Messages de Juanito{" "}
{unreadCount > 0 && (
<span className="ml-1 rounded-full bg-primary-600 px-2 py-0.5 text-xs font-bold text-white">
{unreadCount} nouveau(x)
</span>
)}
</h2>
</div>
<div className="space-y-3">
{messages.length === 0 && (
<p className="card text-sm text-white/50">
Aucun message pour le moment. Juanito ne va pas tarder !
</p>
)}
{messages.map((msg) => (
<div
key={msg.id}
className={`card ${!msg.isRead ? "border-primary-500/40 bg-primary-500/5" : ""}`}
onClick={() => !msg.isRead && markAsRead(msg.id)}
>
<div className="flex items-start justify-between gap-3">
<div>
<h3 className="font-semibold">{msg.title}</h3>
<p className="mt-1 whitespace-pre-line text-sm text-white/75">
{msg.content}
</p>
<p className="mt-2 text-xs text-white/40">
{new Date(msg.createdAt).toLocaleDateString("fr-FR", {
day: "numeric",
month: "long",
year: "numeric",
})}
</p>
</div>
{!msg.isRead && (
<span className="mt-1 h-3 w-3 shrink-0 rounded-full bg-primary-500" />
)}
</div>
</div>
))}
</div>
</section>
{/* Section Performance */}
<section className="mb-6">
<div className="mb-3 flex items-center justify-between">
<h2 className="text-lg font-bold">🏅 Ta performance</h2>
<button
onClick={() => setShowMyPerf(!showMyPerf)}
className="text-sm text-primary-300 hover:text-primary-200"
>
{showMyPerf ? "Voir la suite" : "+ Ajouter un chrono"}
</button>
</div>
{showMyPerf && <AddPerfForm profileId={profile?.id} onDone={() => { setShowMyPerf(false); router.refresh(); }} />}
<div className="grid grid-cols-2 gap-3">
<div className="card text-center">
<p className="text-xs text-white/50">🪢 Meilleur chrono corde</p>
<p className="mt-1 text-2xl font-bold text-primary-300">
{formatDuration(profile?.jumpRopeBestTime)}
</p>
</div>
<div className="card text-center">
<p className="text-xs text-white/50">💪 100 burpees</p>
<p className="mt-1 text-2xl font-bold text-primary-300">
{formatDuration(profile?.burpee100BestTime)}
</p>
</div>
</div>
<div className="mt-3 card">
<h3 className="mb-2 text-sm font-semibold text-white/70">
Historique des entraînements
</h3>
{profile?.sessions?.length === 0 && (
<p className="text-sm text-white/50">
Aucun entraînement enregistré pour l&apos;instant.
</p>
)}
<div className="max-h-56 space-y-2 overflow-y-auto">
{profile?.sessions?.map((s: any) => (
<div
key={s.id}
className="flex items-center justify-between rounded-xl bg-white/5 px-3 py-2 text-sm"
>
<span className="text-white/70">
{new Date(s.date).toLocaleDateString("fr-FR", {
day: "2-digit",
month: "short",
})}
</span>
<span className="flex gap-3 text-xs tabular-nums text-white/50">
{s.jumpRopeTime && (
<span>🪢 {formatDuration(s.jumpRopeTime)}</span>
)}
{s.burpee100Time && (
<span>💪 {formatDuration(s.burpee100Time)}</span>
)}
</span>
</div>
))}
</div>
</div>
</section>
{/* Actions rapides */}
<section className="mb-6 grid grid-cols-2 gap-3">
<Link href="/member/muay-thai/certificate" className="card flex flex-col items-center justify-center py-5 text-center transition-colors hover:bg-white/10">
<span className="mb-2 text-3xl">📄</span>
<span className="text-sm font-semibold">Certificat médical</span>
<span className="mt-1 text-xs text-white/50">
{user.certificates?.some((c: any) => c.status === "VALIDATED")
? "✓ Validé"
: user.certificates?.some((c: any) => c.status === "PENDING")
? "⏳ En cours"
: "À fournir"}
</span>
</Link>
<Link href="/member/muay-thai/absence" className="card flex flex-col items-center justify-center py-5 text-center transition-colors hover:bg-white/10">
<span className="mb-2 text-3xl">📅</span>
<span className="text-sm font-semibold">Signaler une absence</span>
</Link>
<button onClick={() => setShowMedia(!showMedia)} className="card flex flex-col items-center justify-center py-5 text-center transition-colors hover:bg-white/10">
<span className="mb-2 text-3xl">🎬</span>
<span className="text-sm font-semibold">Photos & vidéos</span>
</button>
<button onClick={() => setShowHomework(!showHomework)} className="card flex flex-col items-center justify-center py-5 text-center transition-colors hover:bg-white/10">
<span className="mb-2 text-3xl">🏋️</span>
<span className="text-sm font-semibold">Trucs à travailler</span>
</button>
</section>
{/* Médias */}
{showMedia && (
<section className="card mb-6">
<h3 className="mb-3 font-bold">🎬 Photos & vidéos des adhérents</h3>
<div className="grid grid-cols-2 gap-3">
{media.length === 0 && (
<p className="col-span-2 text-sm text-white/50">
Pas encore de médias publiés.
</p>
)}
{media.map((m) =>
m.type === "PHOTO" ? (
<a
key={m.id}
href={m.url}
target="_blank"
rel="noopener noreferrer"
className="overflow-hidden rounded-xl bg-white/5"
>
<img
src={m.url}
alt={m.title}
className="aspect-square w-full object-cover"
loading="lazy"
/>
<p className="truncate p-2 text-xs text-white/70">
{m.title}
</p>
</a>
) : (
<div key={m.id} className="overflow-hidden rounded-xl bg-white/5">
<video controls playsInline preload="metadata" className="aspect-video w-full" src={m.url} />
<p className="truncate p-2 text-xs text-white/70">{m.title}</p>
</div>
)
)}
</div>
</section>
)}
{/* Homework */}
{showHomework && (
<section className="card mb-6">
<h3 className="mb-3 font-bold">🏋️ Exercices à travailler</h3>
{user.homework?.length === 0 && (
<p className="text-sm text-white/50">
Aucun exercice pour l&apos;instant. Juanito va t&apos;en envoyer !
</p>
)}
<div className="space-y-3">
{user.homework?.map((h: any) => (
<div key={h.id} className="rounded-xl bg-white/5 p-4">
<h4 className="font-semibold text-primary-300">{h.title}</h4>
<p className="mt-1 whitespace-pre-line text-sm text-white/75">
{h.description}
</p>
{h.videoUrl && (
<video
controls
playsInline
preload="metadata"
className="mt-3 aspect-video w-full rounded-xl"
src={h.videoUrl}
/>
)}
<p className="mt-2 text-xs text-white/40">
{new Date(h.createdAt).toLocaleDateString("fr-FR")}
</p>
</div>
))}
</div>
</section>
)}
</div>
</main>
);
}
function AddPerfForm({
profileId,
onDone,
}: {
profileId?: string;
onDone: () => void;
}) {
const [jumpRopeTime, setJumpRopeTime] = useState("");
const [burpee100Time, setBurpee100Time] = useState("");
const [notes, setNotes] = useState("");
const [saving, setSaving] = useState(false);
const [error, setError] = useState("");
function parseTimeToSeconds(input: string): number | null {
// Accepte "3:45", "3m45", "45" (secondes)
const clean = input.trim();
if (!clean) return null;
const colonMatch = clean.match(/^(\d+):(\d{1,2})$/);
if (colonMatch) {
return parseInt(colonMatch[1]) * 60 + parseInt(colonMatch[2]);
}
const mMatch = clean.match(/^(\d+)m(\d{1,2})$/i);
if (mMatch) {
return parseInt(mMatch[1]) * 60 + parseInt(mMatch[2]);
}
const sMatch = clean.match(/^(\d+)s?$/i);
if (sMatch) return parseInt(sMatch[1]);
return null;
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!jumpRopeTime && !burpee100Time) {
setError("Renseigne au moins un chrono");
return;
}
setSaving(true);
setError("");
const payload: any = {};
const jr = parseTimeToSeconds(jumpRopeTime);
const bp = parseTimeToSeconds(burpee100Time);
if (jr !== null) payload.jumpRopeTime = jr;
if (bp !== null) payload.burpee100Time = bp;
if (notes) payload.notes = notes;
const res = await fetch("/api/training", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (res.ok) {
onDone();
} else {
setError("Erreur lors de l'enregistrement");
setSaving(false);
}
}
return (
<form onSubmit={handleSubmit} className="card mb-3 border-primary-500/30">
<h3 className="mb-3 font-semibold">⏱️ Enregistrer un chrono</h3>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="label">🪢 Corde à sauter</label>
<input
className="input"
placeholder="ex: 3:45 ou 225"
value={jumpRopeTime}
onChange={(e) => setJumpRopeTime(e.target.value)}
/>
</div>
<div>
<label className="label">💪 100 burpees</label>
<input
className="input"
placeholder="ex: 5:30"
value={burpee100Time}
onChange={(e) => setBurpee100Time(e.target.value)}
/>
</div>
</div>
<div className="mt-3">
<label className="label">Notes (optionnel)</label>
<input
className="input"
placeholder="Ta séance en quelques mots..."
value={notes}
onChange={(e) => setNotes(e.target.value)}
/>
</div>
{error && <p className="mt-2 text-sm text-red-400">{error}</p>}
<button type="submit" className="btn-primary mt-3 w-full" disabled={saving}>
{saving ? "Enregistrement..." : "✅ Enregistrer"}
</button>
</form>
);
}