From f8ec63b5ef2868d67724a6d1a19dd45f43cfb243 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 12 Nov 2025 09:24:16 +0000 Subject: [PATCH 1/2] Feature: Remplacement de tous les prompts par des modaux + Fix RSS refresh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modaux : - Création ConfirmModal pour confirmations avec variants (default/destructive) - Création InputModal pour saisie de texte/URL/mot de passe - Création AddUserModal pour création d'utilisateur avec admin checkbox - Remplacement de tous les prompt() et confirm() par des modaux modernes - Modaux pour : suppression note, ajout todo, ajout RSS, ajout utilisateur, suppression utilisateur, changement mot de passe, ajout todo dans note Fix RSS : - Ajout route POST /api/rss/refresh (alias de /api/rss/fetch) - Fix uploadImage retour avec result.image || result UX améliorée : - Plus de popups navigateur natifs - Interface cohérente et moderne - Validation dans les modaux - Support touche Entrée dans les InputModals - Auto-focus sur le champ de saisie --- routes/rss.routes.js | 20 +++ src/components/modals/AddUserModal.tsx | 95 +++++++++++ src/components/modals/ConfirmModal.tsx | 57 +++++++ src/components/modals/InputModal.tsx | 89 ++++++++++ src/pages/Index.tsx | 220 ++++++++++++++++--------- src/services/NotesService.ts | 9 +- 6 files changed, 409 insertions(+), 81 deletions(-) create mode 100644 src/components/modals/AddUserModal.tsx create mode 100644 src/components/modals/ConfirmModal.tsx create mode 100644 src/components/modals/InputModal.tsx diff --git a/routes/rss.routes.js b/routes/rss.routes.js index 6504dad..92865f2 100644 --- a/routes/rss.routes.js +++ b/routes/rss.routes.js @@ -130,6 +130,26 @@ router.post('/fetch', requireAdmin, async (req, res) => { } }); +/** + * POST /api/rss/refresh + * Alias pour fetch (pour compatibilité) + */ +router.post('/refresh', requireAdmin, async (req, res) => { + try { + const rssScheduler = require('../services/rss-scheduler'); + await rssScheduler.manualFetch(); + + // Invalider le cache + articlesCache = null; + articlesCacheTime = 0; + + res.json({ message: 'Mise à jour des flux RSS terminée avec succès' }); + } catch (error) { + logger.error('Erreur lors du refresh des flux RSS:', error); + res.status(500).json({ error: 'Erreur serveur' }); + } +}); + // Cache pour les articles RSS (30 secondes) let articlesCache = null; let articlesCacheTime = 0; diff --git a/src/components/modals/AddUserModal.tsx b/src/components/modals/AddUserModal.tsx new file mode 100644 index 0000000..f83f945 --- /dev/null +++ b/src/components/modals/AddUserModal.tsx @@ -0,0 +1,95 @@ +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Checkbox } from "@/components/ui/checkbox"; +import { useState, useEffect } from "react"; +import { UserPlus } from "lucide-react"; + +interface AddUserModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onConfirm: (username: string, password: string, isAdmin: boolean) => void; +} + +export function AddUserModal({ open, onOpenChange, onConfirm }: AddUserModalProps) { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [isAdmin, setIsAdmin] = useState(false); + + useEffect(() => { + if (open) { + setUsername(""); + setPassword(""); + setIsAdmin(false); + } + }, [open]); + + const handleConfirm = () => { + if (username.trim() && password.trim()) { + onConfirm(username.trim(), password.trim(), isAdmin); + onOpenChange(false); + } + }; + + const isValid = username.trim().length > 0 && password.trim().length > 0; + + return ( + + + + + + Ajouter un utilisateur + + + Créez un nouveau compte utilisateur pour l'application + + +
+
+ + setUsername(e.target.value)} + autoFocus + /> +
+
+ + setPassword(e.target.value)} + /> +
+
+ setIsAdmin(checked as boolean)} + /> + +
+
+ + + + +
+
+ ); +} diff --git a/src/components/modals/ConfirmModal.tsx b/src/components/modals/ConfirmModal.tsx new file mode 100644 index 0000000..20af3f8 --- /dev/null +++ b/src/components/modals/ConfirmModal.tsx @@ -0,0 +1,57 @@ +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { AlertTriangle } from "lucide-react"; + +interface ConfirmModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + title: string; + description: string; + onConfirm: () => void; + confirmText?: string; + cancelText?: string; + variant?: "default" | "destructive"; +} + +export function ConfirmModal({ + open, + onOpenChange, + title, + description, + onConfirm, + confirmText = "Confirmer", + cancelText = "Annuler", + variant = "default" +}: ConfirmModalProps) { + const handleConfirm = () => { + onConfirm(); + onOpenChange(false); + }; + + return ( + + + + + {variant === "destructive" && } + {title} + + + {description} + + + + + + + + + ); +} diff --git a/src/components/modals/InputModal.tsx b/src/components/modals/InputModal.tsx new file mode 100644 index 0000000..2409ad3 --- /dev/null +++ b/src/components/modals/InputModal.tsx @@ -0,0 +1,89 @@ +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { useState, useEffect } from "react"; + +interface InputModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + title: string; + description?: string; + label: string; + placeholder?: string; + defaultValue?: string; + onConfirm: (value: string) => void; + confirmText?: string; + cancelText?: string; + type?: "text" | "password" | "url" | "email"; +} + +export function InputModal({ + open, + onOpenChange, + title, + description, + label, + placeholder, + defaultValue = "", + onConfirm, + confirmText = "Confirmer", + cancelText = "Annuler", + type = "text" +}: InputModalProps) { + const [value, setValue] = useState(defaultValue); + + useEffect(() => { + if (open) { + setValue(defaultValue); + } + }, [open, defaultValue]); + + const handleConfirm = () => { + if (value.trim()) { + onConfirm(value.trim()); + onOpenChange(false); + setValue(""); + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter" && value.trim()) { + e.preventDefault(); + handleConfirm(); + } + }; + + return ( + + + + {title} + {description && {description}} + +
+
+ + setValue(e.target.value)} + onKeyDown={handleKeyDown} + autoFocus + /> +
+
+ + + + +
+
+ ); +} diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 9348abc..180f314 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -24,6 +24,9 @@ import SettingsService, { Settings as AppSettings } from "@/services/SettingsSer import CalendarService, { CalendarEvent } from "@/services/CalendarService"; import { useNavigate } from "react-router-dom"; import { showError, showSuccess } from "@/utils/toast"; +import { ConfirmModal } from "@/components/modals/ConfirmModal"; +import { InputModal } from "@/components/modals/InputModal"; +import { AddUserModal } from "@/components/modals/AddUserModal"; interface UserType { id: number; @@ -47,6 +50,15 @@ const Index = () => { const [showArchived, setShowArchived] = useState(false); const navigate = useNavigate(); + // Modal states + const [deleteNoteModal, setDeleteNoteModal] = useState(false); + const [addTodoModal, setAddTodoModal] = useState(false); + const [addRssFeedModal, setAddRssFeedModal] = useState(false); + const [addUserModal, setAddUserModal] = useState(false); + const [deleteUserModal, setDeleteUserModal] = useState<{open: boolean, userId?: number}>({open: false}); + const [changePasswordModal, setChangePasswordModal] = useState<{open: boolean, userId?: number}>({open: false}); + const [addNoteTodoModal, setAddNoteTodoModal] = useState(false); + useEffect(() => { const userFromAuth = AuthService.getUser(); if (userFromAuth) { @@ -195,37 +207,32 @@ const Index = () => { } }; - const handleDeleteNote = async () => { + const confirmDeleteNote = async () => { if (!openNote?.id) return; - if (window.confirm("Êtes-vous sûr de vouloir supprimer cette note ?")) { - try { - const success = await NotesService.deleteNote(openNote.id); + try { + const success = await NotesService.deleteNote(openNote.id); - if (success) { - const updatedNotes = notes.filter(note => note.id !== openNote.id); - setNotes(updatedNotes); - setOpenNote(null); - showSuccess("Note supprimée"); - } - } catch (error) { - showError("Erreur lors de la suppression"); + if (success) { + const updatedNotes = notes.filter(note => note.id !== openNote.id); + setNotes(updatedNotes); + setOpenNote(null); + showSuccess("Note supprimée"); } + } catch (error) { + showError("Erreur lors de la suppression"); } }; - const handleAddTodo = async () => { - const text = prompt("Nouvelle tâche:"); - if (text) { - try { - const newTodo = await TodosService.createTodo(text); - if (newTodo) { - setTodos([newTodo, ...todos]); - showSuccess("Tâche ajoutée"); - } - } catch (error) { - showError("Erreur lors de l'ajout de la tâche"); + const confirmAddTodo = async (text: string) => { + try { + const newTodo = await TodosService.createTodo(text); + if (newTodo) { + setTodos([newTodo, ...todos]); + showSuccess("Tâche ajoutée"); } + } catch (error) { + showError("Erreur lors de l'ajout de la tâche"); } }; @@ -248,19 +255,16 @@ const Index = () => { } }; - const handleAddRssFeed = async () => { - const url = prompt("URL du flux RSS:"); - if (url) { - try { - const newFeed = await RssService.addFeed(url); - if (newFeed) { - setRssFeeds([...rssFeeds, newFeed]); - showSuccess("Flux RSS ajouté"); - loadRssArticles(); - } - } catch (error) { - showError("Erreur lors de l'ajout du flux"); + const confirmAddRssFeed = async (url: string) => { + try { + const newFeed = await RssService.addFeed(url); + if (newFeed) { + setRssFeeds([...rssFeeds, newFeed]); + showSuccess("Flux RSS ajouté"); + loadRssArticles(); } + } catch (error) { + showError("Erreur lors de l'ajout du flux"); } }; @@ -285,13 +289,7 @@ const Index = () => { } }; - const handleAddUser = async () => { - const username = prompt("Nom d'utilisateur:"); - if (!username) return; - const password = prompt("Mot de passe:"); - if (!password) return; - const isAdmin = window.confirm("Donner les droits administrateur ?"); - + const confirmAddUser = async (username: string, password: string, isAdmin: boolean) => { try { const newUser = await AdminService.createUser(username, password, isAdmin); if (newUser) { @@ -303,31 +301,30 @@ const Index = () => { } }; - const handleDeleteUser = async (userId: number) => { - if (window.confirm("Êtes-vous sûr de vouloir supprimer cet utilisateur ?")) { - try { - const success = await AdminService.deleteUser(userId); - if (success) { - loadUsers(); - showSuccess("Utilisateur supprimé"); - } - } catch (error) { - showError("Erreur lors de la suppression"); + const confirmDeleteUser = async () => { + if (!deleteUserModal.userId) return; + + try { + const success = await AdminService.deleteUser(deleteUserModal.userId); + if (success) { + loadUsers(); + showSuccess("Utilisateur supprimé"); } + } catch (error) { + showError("Erreur lors de la suppression"); } }; - const handleChangePassword = async (userId: number) => { - const newPassword = prompt("Nouveau mot de passe:"); - if (newPassword) { - try { - const success = await AdminService.updateUserPassword(userId, newPassword); - if (success) { - showSuccess("Mot de passe modifié"); - } - } catch (error) { - showError("Erreur lors de la modification"); + const confirmChangePassword = async (newPassword: string) => { + if (!changePasswordModal.userId) return; + + try { + const success = await AdminService.updateUserPassword(changePasswordModal.userId, newPassword); + if (success) { + showSuccess("Mot de passe modifié"); } + } catch (error) { + showError("Erreur lors de la modification"); } }; @@ -548,7 +545,7 @@ const Index = () => { @@ -867,7 +858,7 @@ const Index = () => { Actualiser - @@ -903,7 +894,7 @@ const Index = () => { @@ -1008,7 +999,7 @@ const Index = () => { @@ -1057,7 +1048,7 @@ const Index = () => {