From 3b8644bc4c92a6ed685cdec99e0f69a9284b6164 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Nov 2025 20:25:33 +0000 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20d=C3=A9placer=20les=20statistiques?= =?UTF-8?q?=20dans=20un=20modal=20popup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Créer un nouveau composant StatsModal pour afficher les statistiques - Ajouter une icône BarChart3 dans le header à côté du bouton de thème - Retirer le StatsDashboard de sa position sous le calendrier - Afficher les statistiques dans un modal au clic sur l'icône --- src/components/StatsModal.tsx | 31 +++++++++++++++++++++++++++++++ src/pages/Index.tsx | 23 +++++++++++++++++++---- 2 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 src/components/StatsModal.tsx diff --git a/src/components/StatsModal.tsx b/src/components/StatsModal.tsx new file mode 100644 index 0000000..2bfd938 --- /dev/null +++ b/src/components/StatsModal.tsx @@ -0,0 +1,31 @@ +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { StatsDashboard } from "./StatsDashboard"; +import { Note } from "@/services/NotesService"; +import { Todo } from "@/services/TodosService"; + +interface StatsModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + notes: Note[]; + todos: Todo[]; +} + +export function StatsModal({ open, onOpenChange, notes, todos }: StatsModalProps) { + return ( + + + + Statistiques + +
+ +
+
+
+ ); +} diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index f6b22a9..7a0d7bb 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -15,7 +15,7 @@ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, Command import { RichTextEditor } from "@/components/RichTextEditor"; import { PlusCircle, Search, User, LogOut, Settings, ChevronDown, Plus, Archive, Trash2, - Image as ImageIcon, CheckSquare, FileText, Rss, ExternalLink, RefreshCw, Key, Zap, Paperclip, X, Edit, Calendar as CalendarIcon, Tag as TagIcon, MessageSquare, Send, Check, ChevronsUpDown, Star, Activity, FileDown, LayoutGrid, List, Sparkles + Image as ImageIcon, CheckSquare, FileText, Rss, ExternalLink, RefreshCw, Key, Zap, Paperclip, X, Edit, Calendar as CalendarIcon, Tag as TagIcon, MessageSquare, Send, Check, ChevronsUpDown, Star, Activity, FileDown, LayoutGrid, List, Sparkles, BarChart3 } from "lucide-react"; import AuthService from "@/services/AuthService"; import AdminService from "@/services/AdminService"; @@ -42,6 +42,7 @@ import { KanbanBoard } from "@/components/KanbanBoard"; import { getSmartTagSuggestions } from "@/utils/smartTags"; import { QuickCaptureWidget } from "@/components/QuickCaptureWidget"; import { StatsDashboard } from "@/components/StatsDashboard"; +import { StatsModal } from "@/components/StatsModal"; import { KeyboardShortcutsHelp } from "@/components/KeyboardShortcutsHelp"; import { useKeyboardShortcuts, type KeyboardShortcut } from "@/hooks/useKeyboardShortcuts"; @@ -1093,6 +1094,15 @@ const Index = () => { Nouvelle Note + + {user && ( @@ -1262,9 +1272,6 @@ const Index = () => { )} - - {/* Statistics Dashboard */} - {/* Middle Column: Notes or Open Note */} @@ -3149,6 +3156,14 @@ const Index = () => { shortcuts={keyboardShortcuts} /> + {/* Statistics Modal */} + + {/* Quick Capture Widget */} From c66e5adc4ac30ee96fe1f011eb2970d46ed1b8e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Nov 2025 20:29:59 +0000 Subject: [PATCH 2/3] fix: corriger l'erreur CSP et le nettoyage HTML des notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remplacer l'audio base64 par Web Audio API dans PomodoroTimer pour éviter les erreurs CSP - Ajouter une fonction cleanHtmlContent pour correctement décoder les entités HTML - Corriger l'affichage des notes pour supprimer les caractères parasites (&nb-, etc.) --- src/components/PomodoroTimer.tsx | 50 +++++++++++++++++++++++++++----- src/pages/Index.tsx | 19 +++++++++++- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/components/PomodoroTimer.tsx b/src/components/PomodoroTimer.tsx index e35b371..ad2b67d 100644 --- a/src/components/PomodoroTimer.tsx +++ b/src/components/PomodoroTimer.tsx @@ -21,12 +21,48 @@ export function PomodoroTimer() { const [isRunning, setIsRunning] = useState(false); const [sessionsCompleted, setSessionsCompleted] = useState(0); const intervalRef = useRef(null); - const audioRef = useRef(null); - useEffect(() => { - // Create audio element for notification - audioRef.current = new Audio('data:audio/wav;base64,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'); - }, []); + // Function to play notification sound using Web Audio API + const playNotificationSound = () => { + try { + const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)(); + const oscillator = audioContext.createOscillator(); + const gainNode = audioContext.createGain(); + + oscillator.connect(gainNode); + gainNode.connect(audioContext.destination); + + // Create a pleasant notification sound (two beeps) + oscillator.frequency.value = 800; + oscillator.type = 'sine'; + + gainNode.gain.setValueAtTime(0.3, audioContext.currentTime); + gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2); + + oscillator.start(audioContext.currentTime); + oscillator.stop(audioContext.currentTime + 0.2); + + // Second beep + setTimeout(() => { + const oscillator2 = audioContext.createOscillator(); + const gainNode2 = audioContext.createGain(); + + oscillator2.connect(gainNode2); + gainNode2.connect(audioContext.destination); + + oscillator2.frequency.value = 1000; + oscillator2.type = 'sine'; + + gainNode2.gain.setValueAtTime(0.3, audioContext.currentTime); + gainNode2.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2); + + oscillator2.start(audioContext.currentTime); + oscillator2.stop(audioContext.currentTime + 0.2); + }, 250); + } catch (e) { + console.error('Audio play failed:', e); + } + }; useEffect(() => { if (isRunning && timeLeft > 0) { @@ -52,9 +88,7 @@ export function PomodoroTimer() { setIsRunning(false); // Play notification sound - if (audioRef.current) { - audioRef.current.play().catch(e => console.error('Audio play failed:', e)); - } + playNotificationSound(); if (mode === 'work') { const newSessionCount = sessionsCompleted + 1; diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 7a0d7bb..7c80868 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -120,6 +120,23 @@ interface UserType { is_admin: boolean; } +// Function to clean HTML content and decode entities +const cleanHtmlContent = (html: string): string => { + if (!html) return ''; + + // Create a temporary div to decode HTML entities + const temp = document.createElement('div'); + temp.innerHTML = html; + + // Get text content (removes HTML tags and decodes entities) + let text = temp.textContent || temp.innerText || ''; + + // Remove any remaining HTML entity artifacts + text = text.replace(/&[a-z]+;?/gi, ' ').replace(/\s+/g, ' ').trim(); + + return text; +}; + const Index = () => { const [user, setUser] = useState(null); const [showAdmin, setShowAdmin] = useState(false); @@ -1398,7 +1415,7 @@ const Index = () => { {note.title || "Sans titre"}

- {note.content ? note.content.replace(/<[^>]*>/g, '') : "Note vide"} + {note.content ? cleanHtmlContent(note.content) : "Note vide"}

{note.todos && note.todos.length > 0 && ( From 2b84ac0e977585acb14d0b72f300039b4015b13a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 28 Nov 2025 20:34:32 +0000 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20transformer=20le=20Pomodoro=20Timer?= =?UTF-8?q?=20en=20modal=20avec=20ic=C3=B4ne=20dans=20le=20header?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Créer un composant PomodoroModal pour afficher le timer - Ajouter une icône Clock dans le header qui affiche le temps restant quand le timer est actif - Modifier PomodoroTimer pour accepter une callback onStateChange - Retirer la box Pomodoro de la page principale - L'icône change de style quand le timer est en cours et affiche le décompte --- src/components/PomodoroModal.tsx | 28 ++++++++++++++++++ src/components/PomodoroTimer.tsx | 13 ++++++++- src/pages/Index.tsx | 49 ++++++++++++++++++++++++++++---- 3 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 src/components/PomodoroModal.tsx diff --git a/src/components/PomodoroModal.tsx b/src/components/PomodoroModal.tsx new file mode 100644 index 0000000..47c83cf --- /dev/null +++ b/src/components/PomodoroModal.tsx @@ -0,0 +1,28 @@ +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { PomodoroTimer } from "./PomodoroTimer"; + +interface PomodoroModalProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onTimerStateChange?: (isRunning: boolean, timeLeft: number, mode: string) => void; +} + +export function PomodoroModal({ open, onOpenChange, onTimerStateChange }: PomodoroModalProps) { + return ( + + + + Pomodoro Timer + +
+ +
+
+
+ ); +} diff --git a/src/components/PomodoroTimer.tsx b/src/components/PomodoroTimer.tsx index ad2b67d..351a055 100644 --- a/src/components/PomodoroTimer.tsx +++ b/src/components/PomodoroTimer.tsx @@ -15,7 +15,11 @@ const TIMER_DURATIONS = { const SESSIONS_BEFORE_LONG_BREAK = 4; -export function PomodoroTimer() { +interface PomodoroTimerProps { + onStateChange?: (isRunning: boolean, timeLeft: number, mode: TimerMode) => void; +} + +export function PomodoroTimer({ onStateChange }: PomodoroTimerProps = {}) { const [mode, setMode] = useState('work'); const [timeLeft, setTimeLeft] = useState(TIMER_DURATIONS.work); const [isRunning, setIsRunning] = useState(false); @@ -64,6 +68,13 @@ export function PomodoroTimer() { } }; + // Notify parent component of state changes + useEffect(() => { + if (onStateChange) { + onStateChange(isRunning, timeLeft, mode); + } + }, [isRunning, timeLeft, mode, onStateChange]); + useEffect(() => { if (isRunning && timeLeft > 0) { intervalRef.current = setInterval(() => { diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 7c80868..153ec6e 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -37,7 +37,7 @@ import { downloadNoteAsMarkdown, downloadAllNotesAsMarkdown, downloadTodosAsMark import { TemplateSelector } from "@/components/TemplateSelector"; import type { NoteTemplate } from "@/utils/noteTemplates"; import { AdvancedSearch, type SearchFilters, type TagOption } from "@/components/AdvancedSearch"; -import { PomodoroTimer } from "@/components/PomodoroTimer"; +import { PomodoroModal } from "@/components/PomodoroModal"; import { KanbanBoard } from "@/components/KanbanBoard"; import { getSmartTagSuggestions } from "@/utils/smartTags"; import { QuickCaptureWidget } from "@/components/QuickCaptureWidget"; @@ -193,6 +193,12 @@ const Index = () => { const [editEventModal, setEditEventModal] = useState<{ open: boolean, event?: CalendarEvent }>({ open: false }); const [showKeyboardHelp, setShowKeyboardHelp] = useState(false); const [showStatsDashboard, setShowStatsDashboard] = useState(false); + const [showPomodoroModal, setShowPomodoroModal] = useState(false); + + // Pomodoro timer states + const [pomodoroRunning, setPomodoroRunning] = useState(false); + const [pomodoroTimeLeft, setPomodoroTimeLeft] = useState(25 * 60); + const [pomodoroMode, setPomodoroMode] = useState<'work' | 'shortBreak' | 'longBreak'>('work'); // Pagination states const [notesPage, setNotesPage] = useState(0); @@ -271,6 +277,20 @@ const Index = () => { return () => clearInterval(interval); }, []); + // Handle Pomodoro timer state changes + const handlePomodoroStateChange = (isRunning: boolean, timeLeft: number, mode: 'work' | 'shortBreak' | 'longBreak') => { + setPomodoroRunning(isRunning); + setPomodoroTimeLeft(timeLeft); + setPomodoroMode(mode); + }; + + // Format time for Pomodoro display + const formatPomodoroTime = (seconds: number): string => { + const mins = Math.floor(seconds / 60); + const secs = seconds % 60; + return `${mins}:${secs.toString().padStart(2, '0')}`; + }; + const loadNotes = async () => { try { const fetchedNotes = await NotesService.getNotes(); @@ -1120,6 +1140,21 @@ const Index = () => { + + {user && ( @@ -1930,11 +1965,8 @@ const Index = () => { )}
- {/* Right Column: Pomodoro Timer, Todos & RSS */} + {/* Right Column: Todos & RSS */}
- {/* Pomodoro Timer */} - - {/* Todos & RSS Boxes side by side */}
{/* Todos Box */} @@ -3181,6 +3213,13 @@ const Index = () => { todos={todos} /> + {/* Pomodoro Timer Modal */} + + {/* Quick Capture Widget */}