feat: add first version of TougliGui with same features as on google sheet

This commit is contained in:
2026-04-21 22:02:20 +02:00
parent 79d5c4baaa
commit f571d8bb3f
53 changed files with 12416 additions and 0 deletions

148
src/components/HomeView.tsx Normal file
View File

@ -0,0 +1,148 @@
import { useStore } from "../store";
export default function HomeView() {
const { guides, openGuide, profiles, activeProfileId } = useStore();
const activeProfile = profiles.find(p => p.id === activeProfileId);
const totalQuests = guides.reduce((s, g) => s + g.total_quests, 0);
const totalCompleted = guides.reduce((s, g) => s + g.completed_quests, 0);
const globalPct = totalQuests > 0 ? Math.round((totalCompleted / totalQuests) * 100) : 0;
const completedGuides = guides.filter(g => g.total_quests > 0 && g.completed_quests === g.total_quests);
const inProgressGuides = guides.filter(g => g.completed_quests > 0 && g.completed_quests < g.total_quests);
return (
<div style={{ flex: 1, overflowY: "auto", padding: "20px 24px" }}>
{/* Header */}
<div style={{ marginBottom: "24px" }}>
<h1 style={{ fontSize: "18px", fontWeight: 700, color: "#f0c040", marginBottom: "4px" }}>
Tougli Guide Dofus
</h1>
{activeProfile && (
<p style={{ fontSize: "13px", color: "#94a3b8" }}>
Profil actif : <span style={{ color: "#e2e8f0", fontWeight: 600 }}>{activeProfile.name}</span>
</p>
)}
</div>
{/* Global progress */}
{guides.length > 0 && (
<div style={{
background: "#161b22", border: "1px solid #2d3748", borderRadius: "10px",
padding: "16px 20px", marginBottom: "24px",
}}>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: "8px" }}>
<span style={{ fontSize: "13px", color: "#94a3b8" }}>Progression globale</span>
<span style={{ fontSize: "13px", fontWeight: 700, color: "#f0c040" }}>
{totalCompleted} / {totalQuests} quêtes ({globalPct}%)
</span>
</div>
<div style={{ height: "6px", background: "#2d3748", borderRadius: "3px", overflow: "hidden" }}>
<div style={{
height: "100%", width: `${globalPct}%`,
background: "linear-gradient(90deg, #4a9eff, #f0c040)",
borderRadius: "3px", transition: "width 0.4s ease",
}} />
</div>
<div style={{ marginTop: "8px", display: "flex", gap: "16px" }}>
<Stat label="Complétés" value={completedGuides.length} color="#4ade80" />
<Stat label="En cours" value={inProgressGuides.length} color="#f0c040" />
<Stat label="Total" value={guides.length} color="#94a3b8" />
</div>
</div>
)}
{/* In progress first */}
{inProgressGuides.length > 0 && (
<Section title="En cours" guides={inProgressGuides} onOpen={openGuide} />
)}
{/* All guides grid */}
<Section title="Tous les guides" guides={guides} onOpen={openGuide} />
</div>
);
}
function Stat({ label, value, color }: { label: string; value: number; color: string }) {
return (
<div style={{ display: "flex", flexDirection: "column", gap: "2px" }}>
<span style={{ fontSize: "16px", fontWeight: 700, color }}>{value}</span>
<span style={{ fontSize: "11px", color: "#4a5568" }}>{label}</span>
</div>
);
}
function Section({ title, guides, onOpen }: {
title: string;
guides: import("../types").GuideListItem[];
onOpen: (gid: string) => void;
}) {
return (
<div style={{ marginBottom: "24px" }}>
<h2 style={{ fontSize: "12px", fontWeight: 600, color: "#4a5568", textTransform: "uppercase", letterSpacing: "0.1em", marginBottom: "10px" }}>
{title}
</h2>
<div style={{
display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(180px, 1fr))", gap: "8px",
}}>
{guides.map(guide => <GuideCard key={guide.gid} guide={guide} onOpen={onOpen} />)}
</div>
</div>
);
}
function GuideCard({ guide, onOpen }: {
guide: import("../types").GuideListItem;
onOpen: (gid: string) => void;
}) {
const pct = guide.total_quests > 0
? Math.round((guide.completed_quests / guide.total_quests) * 100)
: 0;
const isDone = pct === 100 && guide.total_quests > 0;
return (
<button
onClick={() => onOpen(guide.gid)}
style={{
background: isDone ? "rgba(74,222,128,0.05)" : "#161b22",
border: `1px solid ${isDone ? "rgba(74,222,128,0.3)" : "#2d3748"}`,
borderRadius: "8px", padding: "12px 14px", cursor: "pointer",
textAlign: "left", transition: "all 0.15s",
}}
onMouseEnter={e => {
(e.currentTarget as HTMLElement).style.borderColor = isDone ? "rgba(74,222,128,0.6)" : "#f0c040";
(e.currentTarget as HTMLElement).style.background = isDone ? "rgba(74,222,128,0.08)" : "#1a2233";
}}
onMouseLeave={e => {
(e.currentTarget as HTMLElement).style.borderColor = isDone ? "rgba(74,222,128,0.3)" : "#2d3748";
(e.currentTarget as HTMLElement).style.background = isDone ? "rgba(74,222,128,0.05)" : "#161b22";
}}
>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "8px" }}>
<span style={{
fontSize: "12px", fontWeight: 600, color: isDone ? "#4ade80" : "#e2e8f0",
lineHeight: 1.3,
}}>
{guide.name}
</span>
{isDone && <span style={{ fontSize: "14px" }}></span>}
</div>
<div style={{ height: "3px", background: "#2d3748", borderRadius: "2px", overflow: "hidden", marginBottom: "6px" }}>
<div style={{
height: "100%", width: `${pct}%`,
background: isDone ? "#4ade80" : pct > 60 ? "#f0c040" : "#4a9eff",
borderRadius: "2px",
}} />
</div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<span style={{ fontSize: "10px", color: "#4a5568" }}>
{guide.completed_quests}/{guide.total_quests} quêtes
</span>
<span style={{ fontSize: "10px", fontWeight: 700, color: isDone ? "#4ade80" : "#94a3b8" }}>
{pct}%
</span>
</div>
</button>
);
}