fix(frontend): finitions DSFR (ThemeToggle orphelin, confirm() -> ConfirmDialog, tooltips)

- supprime ThemeToggle orphelin (inerte, forçait le thème sombre)
- remplace les confirm() natifs par ConfirmDialog DSFR sur les 3 pages admin
  (utilisateurs, contenus, émotions)
- tooltips Recharts en variables DSFR (lisibles en clair ET sombre)

tsc 0, build OK.
This commit is contained in:
Azmog
2026-06-19 10:57:01 +02:00
parent fe9e921d73
commit f18fb5450b
5 changed files with 73 additions and 74 deletions
@@ -6,6 +6,7 @@ import type { Feed } from '@/types';
import Button from '@/components/ui/Button';
import Input from '@/components/ui/Input';
import Modal from '@/components/ui/Modal';
import ConfirmDialog from '@/components/ui/ConfirmDialog';
import toast from 'react-hot-toast';
export default function AdminContenusPage() {
@@ -14,6 +15,7 @@ export default function AdminContenusPage() {
const [modalOpen, setModalOpen] = useState(false);
const [editingFeed, setEditingFeed] = useState<Feed | null>(null);
const [formLoading, setFormLoading] = useState(false);
const [deleteId, setDeleteId] = useState<number | null>(null);
const [form, setForm] = useState({
titre: '',
contenu: '',
@@ -74,14 +76,16 @@ export default function AdminContenusPage() {
}
};
const handleDelete = async (id: number) => {
if (!confirm('Supprimer cet article ?')) return;
const confirmDelete = async () => {
if (deleteId === null) return;
try {
await api.delete(`/admin/feeds/${id}`);
await api.delete(`/admin/feeds/${deleteId}`);
await fetchFeeds();
toast.success('Article supprimé');
} catch {
toast.error('Erreur lors de la suppression');
} finally {
setDeleteId(null);
}
};
@@ -152,7 +156,7 @@ export default function AdminContenusPage() {
<li>
<button
type="button"
onClick={() => handleDelete(feed.id)}
onClick={() => setDeleteId(feed.id)}
className="fr-btn fr-btn--sm fr-btn--tertiary fr-btn--icon-left fr-icon-delete-line"
title="Supprimer"
>
@@ -238,6 +242,16 @@ export default function AdminContenusPage() {
</ul>
</form>
</Modal>
<ConfirmDialog
isOpen={deleteId !== null}
title="Supprimer l'article"
message="Cet article sera supprimé définitivement. Cette action est irréversible."
confirmLabel="Supprimer"
destructive
onConfirm={confirmDelete}
onClose={() => setDeleteId(null)}
/>
</div>
);
}
@@ -6,6 +6,7 @@ import type { Emotion } from '@/types';
import Button from '@/components/ui/Button';
import Input from '@/components/ui/Input';
import Modal from '@/components/ui/Modal';
import ConfirmDialog from '@/components/ui/ConfirmDialog';
import toast from 'react-hot-toast';
export default function AdminEmotionsPage() {
@@ -14,6 +15,7 @@ export default function AdminEmotionsPage() {
const [modalOpen, setModalOpen] = useState(false);
const [editingEmotion, setEditingEmotion] = useState<Emotion | null>(null);
const [formLoading, setFormLoading] = useState(false);
const [deleteId, setDeleteId] = useState<number | null>(null);
const [form, setForm] = useState({
nom: '',
couleur: '#fce117',
@@ -86,14 +88,18 @@ export default function AdminEmotionsPage() {
}
};
const handleDelete = async (id: number) => {
if (!confirm('Supprimer cette émotion ?')) return;
const handleDelete = (id: number) => setDeleteId(id);
const confirmDelete = async () => {
if (deleteId === null) return;
try {
await api.delete(`/admin/emotions/${id}`);
await api.delete(`/admin/emotions/${deleteId}`);
await fetchEmotions();
toast.success('Émotion supprimée');
} catch {
toast.error('Erreur lors de la suppression');
} finally {
setDeleteId(null);
}
};
@@ -253,6 +259,16 @@ export default function AdminEmotionsPage() {
</ul>
</form>
</Modal>
<ConfirmDialog
isOpen={deleteId !== null}
title="Supprimer l'émotion"
message="Cette émotion (et ses sous-émotions) sera supprimée. Cette action est irréversible."
confirmLabel="Supprimer"
destructive
onConfirm={confirmDelete}
onClose={() => setDeleteId(null)}
/>
</div>
);
}
@@ -6,6 +6,7 @@ import type { Utilisateur } from '@/types';
import Button from '@/components/ui/Button';
import Input from '@/components/ui/Input';
import Modal from '@/components/ui/Modal';
import ConfirmDialog from '@/components/ui/ConfirmDialog';
import toast from 'react-hot-toast';
export default function AdminUtilisateursPage() {
@@ -14,6 +15,7 @@ export default function AdminUtilisateursPage() {
const [search, setSearch] = useState('');
const [createModalOpen, setCreateModalOpen] = useState(false);
const [formLoading, setFormLoading] = useState(false);
const [deleteId, setDeleteId] = useState<number | null>(null);
const [newUser, setNewUser] = useState({
nom: '', prenom: '', email: '', password: '', password_confirmation: '', role_id: 2,
});
@@ -41,14 +43,16 @@ export default function AdminUtilisateursPage() {
}
};
const handleDelete = async (id: number) => {
if (!confirm('Supprimer cet utilisateur ?')) return;
const confirmDelete = async () => {
if (deleteId === null) return;
try {
await api.delete(`/admin/utilisateurs/${id}`);
await api.delete(`/admin/utilisateurs/${deleteId}`);
await fetchUsers();
toast.success('Utilisateur supprimé');
} catch {
toast.error('Erreur lors de la suppression');
} finally {
setDeleteId(null);
}
};
@@ -170,7 +174,7 @@ export default function AdminUtilisateursPage() {
<li>
<button
type="button"
onClick={() => handleDelete(user.id)}
onClick={() => setDeleteId(user.id)}
className="fr-btn fr-btn--sm fr-btn--tertiary fr-btn--icon-left fr-icon-delete-line"
title="Supprimer"
>
@@ -222,6 +226,16 @@ export default function AdminUtilisateursPage() {
</ul>
</form>
</Modal>
<ConfirmDialog
isOpen={deleteId !== null}
title="Supprimer l'utilisateur"
message="Cet utilisateur sera supprimé. Cette action est irréversible."
confirmLabel="Supprimer"
destructive
onConfirm={confirmDelete}
onClose={() => setDeleteId(null)}
/>
</div>
);
}
@@ -60,11 +60,13 @@ export default function EmotionChart({ data, type = 'repartition' }: EmotionChar
[`${value} saisies`, name]
}
contentStyle={{
backgroundColor: 'rgba(255,255,255,0.95)',
border: 'none',
borderRadius: '12px',
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
backgroundColor: 'var(--background-overlap-grey)',
border: '1px solid var(--border-default-grey)',
borderRadius: '0.25rem',
color: 'var(--text-default-grey)',
}}
labelStyle={{ color: 'var(--text-default-grey)' }}
itemStyle={{ color: 'var(--text-default-grey)' }}
/>
</PieChart>
</ResponsiveContainer>
@@ -94,11 +96,13 @@ export default function EmotionChart({ data, type = 'repartition' }: EmotionChar
<YAxis tick={{ fontSize: 12 }} allowDecimals={false} />
<Tooltip
contentStyle={{
backgroundColor: 'rgba(255,255,255,0.95)',
border: 'none',
borderRadius: '12px',
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
backgroundColor: 'var(--background-overlap-grey)',
border: '1px solid var(--border-default-grey)',
borderRadius: '0.25rem',
color: 'var(--text-default-grey)',
}}
labelStyle={{ color: 'var(--text-default-grey)' }}
itemStyle={{ color: 'var(--text-default-grey)' }}
/>
<Legend />
{data.evolution.emotions.map((emotion) => (
@@ -134,11 +138,13 @@ export default function EmotionChart({ data, type = 'repartition' }: EmotionChar
<YAxis domain={[0, 10]} tick={{ fontSize: 12 }} />
<Tooltip
contentStyle={{
backgroundColor: 'rgba(255,255,255,0.95)',
border: 'none',
borderRadius: '12px',
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
backgroundColor: 'var(--background-overlap-grey)',
border: '1px solid var(--border-default-grey)',
borderRadius: '0.25rem',
color: 'var(--text-default-grey)',
}}
labelStyle={{ color: 'var(--text-default-grey)' }}
itemStyle={{ color: 'var(--text-default-grey)' }}
formatter={(value: ValueType | undefined) => [`${Number(value).toFixed(1)}`, 'Intensité moyenne']}
/>
<Bar dataKey="intensite" fill="#000091" radius={[6, 6, 0, 0]} />
@@ -1,51 +0,0 @@
"use client";
import React from "react";
type Scheme = "light" | "dark";
export default function ThemeToggle() {
const [mounted, setMounted] = React.useState(false);
const [scheme, setScheme] = React.useState<Scheme>("light");
React.useEffect(() => {
setMounted(true);
const current = document.documentElement.getAttribute("data-fr-theme");
setScheme(current === "dark" ? "dark" : "light");
}, []);
const toggle = () => {
const next: Scheme = scheme === "dark" ? "light" : "dark";
document.documentElement.setAttribute("data-fr-theme", next);
document.documentElement.setAttribute("data-fr-scheme", next);
setScheme(next);
};
if (!mounted) {
return (
<button
type="button"
className="fr-btn fr-btn--tertiary fr-btn--icon-left fr-icon-theme-fill"
aria-label="Changer le thème"
>
Thème
</button>
);
}
return (
<button
type="button"
onClick={toggle}
className={`fr-btn fr-btn--tertiary fr-btn--icon-left ${
scheme === "dark" ? "fr-icon-sun-fill" : "fr-icon-moon-fill"
}`}
aria-label={
scheme === "dark" ? "Passer en mode clair" : "Passer en mode sombre"
}
title={scheme === "dark" ? "Passer en mode clair" : "Passer en mode sombre"}
>
{scheme === "dark" ? "Mode clair" : "Mode sombre"}
</button>
);
}