diff --git a/src/components/TemplateSelector.tsx b/src/components/TemplateSelector.tsx new file mode 100644 index 0000000..fa58cae --- /dev/null +++ b/src/components/TemplateSelector.tsx @@ -0,0 +1,126 @@ +import { useState } from "react"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Badge } from "@/components/ui/badge"; +import { Search, Sparkles } from "lucide-react"; +import { DEFAULT_TEMPLATES, getTemplatesByCategory, searchTemplates, type NoteTemplate } from "@/utils/noteTemplates"; + +interface TemplateSelectorProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onSelectTemplate: (template: NoteTemplate) => void; +} + +export function TemplateSelector({ open, onOpenChange, onSelectTemplate }: TemplateSelectorProps) { + const [searchQuery, setSearchQuery] = useState(""); + + const templates = searchQuery.trim() + ? searchTemplates(searchQuery) + : DEFAULT_TEMPLATES; + + const templatesByCategory = getTemplatesByCategory(); + + const handleSelectTemplate = (template: NoteTemplate) => { + onSelectTemplate(template); + onOpenChange(false); + setSearchQuery(""); + }; + + return ( + + + + + + Choisir un template + + + Démarrez rapidement avec un modèle prédéfini + + + + {/* Search */} +
+ + setSearchQuery(e.target.value)} + className="pl-9" + /> +
+ + {/* Templates Grid */} + + {searchQuery.trim() ? ( + // Search Results +
+ {templates.map((template) => ( + handleSelectTemplate(template)} + /> + ))} + {templates.length === 0 && ( +
+ Aucun template trouvé +
+ )} +
+ ) : ( + // Templates by Category +
+ {Array.from(templatesByCategory.entries()).map(([category, categoryTemplates]) => ( +
+

+ {category} +

+
+ {categoryTemplates.map((template) => ( + handleSelectTemplate(template)} + /> + ))} +
+
+ ))} +
+ )} +
+
+
+ ); +} + +interface TemplateCardProps { + template: NoteTemplate; + onClick: () => void; +} + +function TemplateCard({ template, onClick }: TemplateCardProps) { + return ( + + ); +} diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 325f07f..8103690 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -34,6 +34,8 @@ import { AddUserModal } from "@/components/modals/AddUserModal"; import { compressImage, formatFileSize } from "@/utils/imageCompression"; import { ModeToggle } from "@/components/mode-toggle"; import { downloadNoteAsMarkdown, downloadAllNotesAsMarkdown, downloadTodosAsMarkdown } from "@/utils/markdownExport"; +import { TemplateSelector } from "@/components/TemplateSelector"; +import type { NoteTemplate } from "@/utils/noteTemplates"; // ===== FONCTIONS UTILITAIRES TIMEZONE EUROPE/PARIS ===== @@ -147,6 +149,7 @@ const Index = () => { 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 [templateSelectorOpen, setTemplateSelectorOpen] = useState(false); const [addNoteTodoModal, setAddNoteTodoModal] = useState(false); const [addTagModal, setAddTagModal] = useState(false); const [selectedImage, setSelectedImage] = useState(null); @@ -410,12 +413,32 @@ const Index = () => { navigate("/login"); }; - const handleCreateNote = async () => { + const handleCreateNote = () => { + setTemplateSelectorOpen(true); + }; + + const handleCreateNoteFromTemplate = async (template: NoteTemplate) => { try { - const newNote = await NotesService.createNote("Nouvelle note"); + const title = template.name === "Note vide" ? "Nouvelle note" : template.name; + const newNote = await NotesService.createNote(title, template.content); if (newNote) { - setNotes([newNote, ...notes]); - setOpenNote(newNote); + // Add todos if template has any + if (template.todos && template.todos.length > 0) { + for (const todo of template.todos) { + await NotesService.addTodo(newNote.id!, todo.text); + } + // Reload note to get the todos + const notes = await NotesService.getNotes(); + const updatedNote = notes.find(n => n.id === newNote.id); + if (updatedNote) { + setNotes([updatedNote, ...notes.filter(n => n.id !== newNote.id)]); + setOpenNote(updatedNote); + } + } else { + setNotes([newNote, ...notes]); + setOpenNote(newNote); + } + showSuccess("Note créée depuis le template"); } } catch (error) { showError("Erreur lors de la création de la note"); @@ -2287,6 +2310,13 @@ const Index = () => { confirmText="Ajouter" /> + {/* Template Selector */} + + {/* Add Event Modal */} diff --git a/src/pages/mobile/NotesPage.tsx b/src/pages/mobile/NotesPage.tsx index d42f085..be1a682 100644 --- a/src/pages/mobile/NotesPage.tsx +++ b/src/pages/mobile/NotesPage.tsx @@ -8,6 +8,8 @@ import { MobileCard } from "@/components/mobile/MobileCard"; import { Plus, Search, CheckSquare, Image as ImageIcon, Paperclip, Tag as TagIcon, FileText } from "lucide-react"; import NotesService, { Note } from "@/services/NotesService"; import { showError, showSuccess } from "@/utils/toast"; +import { TemplateSelector } from "@/components/TemplateSelector"; +import type { NoteTemplate } from "@/utils/noteTemplates"; const NOTES_PER_PAGE = 10; @@ -16,6 +18,7 @@ export default function NotesPage() { const [searchQuery, setSearchQuery] = useState(""); const [showArchived, setShowArchived] = useState(false); const [page, setPage] = useState(0); + const [templateSelectorOpen, setTemplateSelectorOpen] = useState(false); const navigate = useNavigate(); const loadNotes = async () => { @@ -48,10 +51,24 @@ export default function NotesPage() { const totalPages = Math.ceil(filteredNotes.length / NOTES_PER_PAGE); const paginatedNotes = filteredNotes.slice(page * NOTES_PER_PAGE, (page + 1) * NOTES_PER_PAGE); - const handleCreateNote = async () => { + const handleCreateNote = () => { + setTemplateSelectorOpen(true); + }; + + const handleCreateNoteFromTemplate = async (template: NoteTemplate) => { try { - const newNote = await NotesService.createNote("Nouvelle note", ""); - navigate(`/mobile/notes/${newNote.id}`); + const title = template.name === "Note vide" ? "Nouvelle note" : template.name; + const newNote = await NotesService.createNote(title, template.content); + if (newNote) { + // Add todos if template has any + if (template.todos && template.todos.length > 0) { + for (const todo of template.todos) { + await NotesService.addTodo(newNote.id!, todo.text); + } + } + showSuccess("Note créée depuis le template"); + navigate(`/mobile/notes/${newNote.id}`); + } } catch (error) { showError("Erreur lors de la création de la note"); } @@ -238,6 +255,13 @@ export default function NotesPage() { {/* FAB */} + + {/* Template Selector */} + ); } diff --git a/src/utils/noteTemplates.ts b/src/utils/noteTemplates.ts new file mode 100644 index 0000000..c31ea22 --- /dev/null +++ b/src/utils/noteTemplates.ts @@ -0,0 +1,363 @@ +export interface NoteTemplate { + id: string; + name: string; + description: string; + icon: string; + category: string; + content: string; + todos?: Array<{ text: string; completed: boolean }>; +} + +export const DEFAULT_TEMPLATES: NoteTemplate[] = [ + { + id: 'blank', + name: 'Note vide', + description: 'Commencer avec une page blanche', + icon: '📄', + category: 'Général', + content: '', + todos: [] + }, + { + id: 'meeting', + name: 'Réunion', + description: 'Notes de réunion structurées', + icon: '🤝', + category: 'Travail', + content: `

Réunion - [Titre]

+

Date: ${new Date().toLocaleDateString('fr-FR')}

+

Participants:

+

Objectif:

+ +

Points discutés

+

1.

+

2.

+

3.

+ +

Décisions

+

•

+ +

Prochaines étapes

+

→

`, + todos: [ + { text: 'Envoyer le compte-rendu', completed: false }, + { text: 'Planifier le suivi', completed: false } + ] + }, + { + id: 'daily-journal', + name: 'Journal quotidien', + description: 'Réflexions et gratitude du jour', + icon: '📔', + category: 'Personnel', + content: `

Journal - ${new Date().toLocaleDateString('fr-FR', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}

+ +

🌅 Ce matin je me sens...

+

+ +

🎯 Mes priorités aujourd'hui

+

1.

+

2.

+

3.

+ +

💡 Idées et réflexions

+

+ +

🙏 Gratitude

+

Aujourd'hui je suis reconnaissant(e) pour:

+

•

+ +

🌙 Bilan de la journée

+

`, + todos: [] + }, + { + id: 'project-planning', + name: 'Planification de projet', + description: 'Organiser un nouveau projet', + icon: '🎯', + category: 'Travail', + content: `

Projet - [Nom du projet]

+ +

📋 Vue d'ensemble

+

Objectif:

+

Deadline:

+

Responsable:

+ +

🎯 Objectifs

+

1.

+

2.

+

3.

+ +

📊 Phases du projet

+

Phase 1: Préparation

+

•

+ +

Phase 2: Développement

+

•

+ +

Phase 3: Finalisation

+

•

+ +

👥 Équipe

+

•

+ +

⚠️ Risques identifiés

+

•

+ +

📌 Ressources nécessaires

+

•

`, + todos: [ + { text: 'Définir le scope', completed: false }, + { text: 'Assembler l\'équipe', completed: false }, + { text: 'Créer le planning', completed: false } + ] + }, + { + id: 'brainstorming', + name: 'Brainstorming', + description: 'Session de génération d\'idées', + icon: '💭', + category: 'Créativité', + content: `

💭 Brainstorming - [Sujet]

+ +

Date: ${new Date().toLocaleDateString('fr-FR')}

+

Question centrale:

+ +

💡 Idées

+

1.

+

2.

+

3.

+

4.

+

5.

+ +

⭐ Idées à creuser

+

•

+ +

🚀 Actions possibles

+

→

+ +

🔗 Connexions et insights

+

`, + todos: [] + }, + { + id: 'recipe', + name: 'Recette de cuisine', + description: 'Noter une recette', + icon: '👨‍🍳', + category: 'Personnel', + content: `

🍳 Recette - [Nom du plat]

+ +

⏱️ Temps de préparation:

+

👥 Portions:

+

😋 Difficulté: Facile / Moyen / Difficile

+ +

📝 Ingrédients

+

•

+ +

👨‍🍳 Préparation

+

1.

+

2.

+

3.

+ +

💡 Astuces

+

•

+ +

📌 Notes

+

`, + todos: [] + }, + { + id: 'weekly-review', + name: 'Revue hebdomadaire', + description: 'Bilan et planification de la semaine', + icon: '📊', + category: 'Productivité', + content: `

📊 Revue de la semaine

+

Semaine du: ${new Date().toLocaleDateString('fr-FR')}

+ +

✅ Accomplissements

+

Cette semaine j'ai réussi à:

+

1.

+

2.

+

3.

+ +

📈 Progrès

+

•

+ +

⚠️ Défis rencontrés

+

•

+ +

💡 Leçons apprises

+

•

+ +

🎯 Objectifs semaine prochaine

+

1.

+

2.

+

3.

+ +

🔄 Habitudes à améliorer

+

•

`, + todos: [ + { text: 'Planifier la semaine prochaine', completed: false } + ] + }, + { + id: 'bug-report', + name: 'Rapport de bug', + description: 'Documenter un problème technique', + icon: '🐛', + category: 'Développement', + content: `

🐛 Bug Report

+ +

Date: ${new Date().toLocaleDateString('fr-FR')}

+

Priorité: 🔴 Critique / 🟠 Haute / 🟡 Moyenne / 🟢 Basse

+

Statut: Nouveau / En cours / Résolu

+ +

📋 Description

+

+ +

🔄 Étapes pour reproduire

+

1.

+

2.

+

3.

+ +

✅ Résultat attendu

+

+ +

❌ Résultat actuel

+

+ +

💻 Environnement

+

OS:

+

Navigateur:

+

Version:

+ +

📌 Notes additionnelles

+

+ +

🔧 Solution proposée

+

`, + todos: [ + { text: 'Reproduire le bug', completed: false }, + { text: 'Identifier la cause', completed: false }, + { text: 'Implémenter le fix', completed: false }, + { text: 'Tester la solution', completed: false } + ] + }, + { + id: 'learning-notes', + name: 'Notes d\'apprentissage', + description: 'Apprendre un nouveau sujet', + icon: '📚', + category: 'Éducation', + content: `

📚 Notes d'apprentissage - [Sujet]

+ +

Date: ${new Date().toLocaleDateString('fr-FR')}

+

Source:

+ +

🎯 Objectifs d'apprentissage

+

•

+ +

📝 Concepts clés

+

Concept 1:

+

•

+ +

Concept 2:

+

•

+ +

💡 Exemples

+

+ +

❓ Questions

+

•

+ +

🔗 Ressources

+

•

+ +

📌 À retenir

+

•

+ +

✍️ Pratique

+

Exercices à faire:

+

1.

`, + todos: [ + { text: 'Réviser les notes', completed: false }, + { text: 'Faire les exercices', completed: false } + ] + }, + { + id: 'travel-planning', + name: 'Planification de voyage', + description: 'Organiser un déplacement', + icon: '✈️', + category: 'Personnel', + content: `

✈️ Voyage - [Destination]

+ +

📅 Dates: du [date] au [date]

+

👥 Voyageurs:

+

💰 Budget:

+ +

🎒 À emporter

+

Vêtements:

+

•

+ +

Documents:

+

• Passeport

+

• Billets

+

• Assurance

+ +

🏨 Hébergement

+

Hôtel/Airbnb:

+

Adresse:

+

Check-in:

+ +

✈️ Transport

+

Vol aller:

+

Vol retour:

+ +

📍 Lieux à visiter

+

1.

+

2.

+

3.

+ +

🍽️ Restaurants recommandés

+

•

+ +

💡 Conseils et infos pratiques

+

•

`, + todos: [ + { text: 'Réserver les vols', completed: false }, + { text: 'Réserver l\'hébergement', completed: false }, + { text: 'Faire la valise', completed: false } + ] + } +]; + +export function getTemplatesByCategory(): Map { + const categorized = new Map(); + + DEFAULT_TEMPLATES.forEach(template => { + const category = template.category; + if (!categorized.has(category)) { + categorized.set(category, []); + } + categorized.get(category)!.push(template); + }); + + return categorized; +} + +export function getTemplateById(id: string): NoteTemplate | undefined { + return DEFAULT_TEMPLATES.find(t => t.id === id); +} + +export function searchTemplates(query: string): NoteTemplate[] { + const lowerQuery = query.toLowerCase(); + return DEFAULT_TEMPLATES.filter(template => + template.name.toLowerCase().includes(lowerQuery) || + template.description.toLowerCase().includes(lowerQuery) || + template.category.toLowerCase().includes(lowerQuery) + ); +}