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:
@@ -6,6 +6,7 @@ import type { Feed } from '@/types';
|
|||||||
import Button from '@/components/ui/Button';
|
import Button from '@/components/ui/Button';
|
||||||
import Input from '@/components/ui/Input';
|
import Input from '@/components/ui/Input';
|
||||||
import Modal from '@/components/ui/Modal';
|
import Modal from '@/components/ui/Modal';
|
||||||
|
import ConfirmDialog from '@/components/ui/ConfirmDialog';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
export default function AdminContenusPage() {
|
export default function AdminContenusPage() {
|
||||||
@@ -14,6 +15,7 @@ export default function AdminContenusPage() {
|
|||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
const [editingFeed, setEditingFeed] = useState<Feed | null>(null);
|
const [editingFeed, setEditingFeed] = useState<Feed | null>(null);
|
||||||
const [formLoading, setFormLoading] = useState(false);
|
const [formLoading, setFormLoading] = useState(false);
|
||||||
|
const [deleteId, setDeleteId] = useState<number | null>(null);
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
titre: '',
|
titre: '',
|
||||||
contenu: '',
|
contenu: '',
|
||||||
@@ -74,14 +76,16 @@ export default function AdminContenusPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (id: number) => {
|
const confirmDelete = async () => {
|
||||||
if (!confirm('Supprimer cet article ?')) return;
|
if (deleteId === null) return;
|
||||||
try {
|
try {
|
||||||
await api.delete(`/admin/feeds/${id}`);
|
await api.delete(`/admin/feeds/${deleteId}`);
|
||||||
await fetchFeeds();
|
await fetchFeeds();
|
||||||
toast.success('Article supprimé');
|
toast.success('Article supprimé');
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('Erreur lors de la suppression');
|
toast.error('Erreur lors de la suppression');
|
||||||
|
} finally {
|
||||||
|
setDeleteId(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -152,7 +156,7 @@ export default function AdminContenusPage() {
|
|||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
type="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"
|
className="fr-btn fr-btn--sm fr-btn--tertiary fr-btn--icon-left fr-icon-delete-line"
|
||||||
title="Supprimer"
|
title="Supprimer"
|
||||||
>
|
>
|
||||||
@@ -238,6 +242,16 @@ export default function AdminContenusPage() {
|
|||||||
</ul>
|
</ul>
|
||||||
</form>
|
</form>
|
||||||
</Modal>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { Emotion } from '@/types';
|
|||||||
import Button from '@/components/ui/Button';
|
import Button from '@/components/ui/Button';
|
||||||
import Input from '@/components/ui/Input';
|
import Input from '@/components/ui/Input';
|
||||||
import Modal from '@/components/ui/Modal';
|
import Modal from '@/components/ui/Modal';
|
||||||
|
import ConfirmDialog from '@/components/ui/ConfirmDialog';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
export default function AdminEmotionsPage() {
|
export default function AdminEmotionsPage() {
|
||||||
@@ -14,6 +15,7 @@ export default function AdminEmotionsPage() {
|
|||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
const [editingEmotion, setEditingEmotion] = useState<Emotion | null>(null);
|
const [editingEmotion, setEditingEmotion] = useState<Emotion | null>(null);
|
||||||
const [formLoading, setFormLoading] = useState(false);
|
const [formLoading, setFormLoading] = useState(false);
|
||||||
|
const [deleteId, setDeleteId] = useState<number | null>(null);
|
||||||
const [form, setForm] = useState({
|
const [form, setForm] = useState({
|
||||||
nom: '',
|
nom: '',
|
||||||
couleur: '#fce117',
|
couleur: '#fce117',
|
||||||
@@ -86,14 +88,18 @@ export default function AdminEmotionsPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = (id: number) => setDeleteId(id);
|
||||||
if (!confirm('Supprimer cette émotion ?')) return;
|
|
||||||
|
const confirmDelete = async () => {
|
||||||
|
if (deleteId === null) return;
|
||||||
try {
|
try {
|
||||||
await api.delete(`/admin/emotions/${id}`);
|
await api.delete(`/admin/emotions/${deleteId}`);
|
||||||
await fetchEmotions();
|
await fetchEmotions();
|
||||||
toast.success('Émotion supprimée');
|
toast.success('Émotion supprimée');
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('Erreur lors de la suppression');
|
toast.error('Erreur lors de la suppression');
|
||||||
|
} finally {
|
||||||
|
setDeleteId(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -253,6 +259,16 @@ export default function AdminEmotionsPage() {
|
|||||||
</ul>
|
</ul>
|
||||||
</form>
|
</form>
|
||||||
</Modal>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { Utilisateur } from '@/types';
|
|||||||
import Button from '@/components/ui/Button';
|
import Button from '@/components/ui/Button';
|
||||||
import Input from '@/components/ui/Input';
|
import Input from '@/components/ui/Input';
|
||||||
import Modal from '@/components/ui/Modal';
|
import Modal from '@/components/ui/Modal';
|
||||||
|
import ConfirmDialog from '@/components/ui/ConfirmDialog';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
|
||||||
export default function AdminUtilisateursPage() {
|
export default function AdminUtilisateursPage() {
|
||||||
@@ -14,6 +15,7 @@ export default function AdminUtilisateursPage() {
|
|||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [createModalOpen, setCreateModalOpen] = useState(false);
|
const [createModalOpen, setCreateModalOpen] = useState(false);
|
||||||
const [formLoading, setFormLoading] = useState(false);
|
const [formLoading, setFormLoading] = useState(false);
|
||||||
|
const [deleteId, setDeleteId] = useState<number | null>(null);
|
||||||
const [newUser, setNewUser] = useState({
|
const [newUser, setNewUser] = useState({
|
||||||
nom: '', prenom: '', email: '', password: '', password_confirmation: '', role_id: 2,
|
nom: '', prenom: '', email: '', password: '', password_confirmation: '', role_id: 2,
|
||||||
});
|
});
|
||||||
@@ -41,14 +43,16 @@ export default function AdminUtilisateursPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async (id: number) => {
|
const confirmDelete = async () => {
|
||||||
if (!confirm('Supprimer cet utilisateur ?')) return;
|
if (deleteId === null) return;
|
||||||
try {
|
try {
|
||||||
await api.delete(`/admin/utilisateurs/${id}`);
|
await api.delete(`/admin/utilisateurs/${deleteId}`);
|
||||||
await fetchUsers();
|
await fetchUsers();
|
||||||
toast.success('Utilisateur supprimé');
|
toast.success('Utilisateur supprimé');
|
||||||
} catch {
|
} catch {
|
||||||
toast.error('Erreur lors de la suppression');
|
toast.error('Erreur lors de la suppression');
|
||||||
|
} finally {
|
||||||
|
setDeleteId(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -170,7 +174,7 @@ export default function AdminUtilisateursPage() {
|
|||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
type="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"
|
className="fr-btn fr-btn--sm fr-btn--tertiary fr-btn--icon-left fr-icon-delete-line"
|
||||||
title="Supprimer"
|
title="Supprimer"
|
||||||
>
|
>
|
||||||
@@ -222,6 +226,16 @@ export default function AdminUtilisateursPage() {
|
|||||||
</ul>
|
</ul>
|
||||||
</form>
|
</form>
|
||||||
</Modal>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,11 +60,13 @@ export default function EmotionChart({ data, type = 'repartition' }: EmotionChar
|
|||||||
[`${value} saisies`, name]
|
[`${value} saisies`, name]
|
||||||
}
|
}
|
||||||
contentStyle={{
|
contentStyle={{
|
||||||
backgroundColor: 'rgba(255,255,255,0.95)',
|
backgroundColor: 'var(--background-overlap-grey)',
|
||||||
border: 'none',
|
border: '1px solid var(--border-default-grey)',
|
||||||
borderRadius: '12px',
|
borderRadius: '0.25rem',
|
||||||
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
|
color: 'var(--text-default-grey)',
|
||||||
}}
|
}}
|
||||||
|
labelStyle={{ color: 'var(--text-default-grey)' }}
|
||||||
|
itemStyle={{ color: 'var(--text-default-grey)' }}
|
||||||
/>
|
/>
|
||||||
</PieChart>
|
</PieChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
@@ -94,11 +96,13 @@ export default function EmotionChart({ data, type = 'repartition' }: EmotionChar
|
|||||||
<YAxis tick={{ fontSize: 12 }} allowDecimals={false} />
|
<YAxis tick={{ fontSize: 12 }} allowDecimals={false} />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
contentStyle={{
|
contentStyle={{
|
||||||
backgroundColor: 'rgba(255,255,255,0.95)',
|
backgroundColor: 'var(--background-overlap-grey)',
|
||||||
border: 'none',
|
border: '1px solid var(--border-default-grey)',
|
||||||
borderRadius: '12px',
|
borderRadius: '0.25rem',
|
||||||
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
|
color: 'var(--text-default-grey)',
|
||||||
}}
|
}}
|
||||||
|
labelStyle={{ color: 'var(--text-default-grey)' }}
|
||||||
|
itemStyle={{ color: 'var(--text-default-grey)' }}
|
||||||
/>
|
/>
|
||||||
<Legend />
|
<Legend />
|
||||||
{data.evolution.emotions.map((emotion) => (
|
{data.evolution.emotions.map((emotion) => (
|
||||||
@@ -134,11 +138,13 @@ export default function EmotionChart({ data, type = 'repartition' }: EmotionChar
|
|||||||
<YAxis domain={[0, 10]} tick={{ fontSize: 12 }} />
|
<YAxis domain={[0, 10]} tick={{ fontSize: 12 }} />
|
||||||
<Tooltip
|
<Tooltip
|
||||||
contentStyle={{
|
contentStyle={{
|
||||||
backgroundColor: 'rgba(255,255,255,0.95)',
|
backgroundColor: 'var(--background-overlap-grey)',
|
||||||
border: 'none',
|
border: '1px solid var(--border-default-grey)',
|
||||||
borderRadius: '12px',
|
borderRadius: '0.25rem',
|
||||||
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
|
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']}
|
formatter={(value: ValueType | undefined) => [`${Number(value).toFixed(1)}`, 'Intensité moyenne']}
|
||||||
/>
|
/>
|
||||||
<Bar dataKey="intensite" fill="#000091" radius={[6, 6, 0, 0]} />
|
<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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user