mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-11 17:29:37 +02:00
feat: add note templates system with 10 pre-built templates
- Create noteTemplates.ts with 10 default templates (Meeting, Daily Journal, Project Planning, etc.) - Add TemplateSelector component with search and category filtering - Integrate template selector in desktop and mobile note creation flows - Support automatic todo creation from templates - Organize templates by category (Travail, Personnel, Créativité, Autre)
This commit is contained in:
4 files changed
+550
-7
No files matched your search
@@ -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 (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="max-w-3xl max-h-[80vh]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center gap-2">
|
||||||
|
<Sparkles className="h-5 w-5 text-primary" />
|
||||||
|
Choisir un template
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Démarrez rapidement avec un modèle prédéfini
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
{/* Search */}
|
||||||
|
<div className="relative">
|
||||||
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
|
<Input
|
||||||
|
placeholder="Rechercher un template..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
className="pl-9"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Templates Grid */}
|
||||||
|
<ScrollArea className="h-[400px] pr-4">
|
||||||
|
{searchQuery.trim() ? (
|
||||||
|
// Search Results
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
|
{templates.map((template) => (
|
||||||
|
<TemplateCard
|
||||||
|
key={template.id}
|
||||||
|
template={template}
|
||||||
|
onClick={() => handleSelectTemplate(template)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{templates.length === 0 && (
|
||||||
|
<div className="col-span-2 text-center py-8 text-muted-foreground">
|
||||||
|
Aucun template trouvé
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
// Templates by Category
|
||||||
|
<div className="space-y-6">
|
||||||
|
{Array.from(templatesByCategory.entries()).map(([category, categoryTemplates]) => (
|
||||||
|
<div key={category}>
|
||||||
|
<h3 className="font-semibold mb-3 text-sm text-muted-foreground uppercase tracking-wide">
|
||||||
|
{category}
|
||||||
|
</h3>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
|
{categoryTemplates.map((template) => (
|
||||||
|
<TemplateCard
|
||||||
|
key={template.id}
|
||||||
|
template={template}
|
||||||
|
onClick={() => handleSelectTemplate(template)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</ScrollArea>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TemplateCardProps {
|
||||||
|
template: NoteTemplate;
|
||||||
|
onClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function TemplateCard({ template, onClick }: TemplateCardProps) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="h-auto p-4 flex flex-col items-start gap-2 hover:bg-accent hover:border-primary transition-all"
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-2 w-full">
|
||||||
|
<span className="text-2xl">{template.icon}</span>
|
||||||
|
<span className="font-semibold text-left flex-1">{template.name}</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground text-left">
|
||||||
|
{template.description}
|
||||||
|
</p>
|
||||||
|
{template.todos && template.todos.length > 0 && (
|
||||||
|
<Badge variant="secondary" className="text-xs">
|
||||||
|
{template.todos.length} tâche{template.todos.length > 1 ? 's' : ''}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
+34
-4
@@ -34,6 +34,8 @@ import { AddUserModal } from "@/components/modals/AddUserModal";
|
|||||||
import { compressImage, formatFileSize } from "@/utils/imageCompression";
|
import { compressImage, formatFileSize } from "@/utils/imageCompression";
|
||||||
import { ModeToggle } from "@/components/mode-toggle";
|
import { ModeToggle } from "@/components/mode-toggle";
|
||||||
import { downloadNoteAsMarkdown, downloadAllNotesAsMarkdown, downloadTodosAsMarkdown } from "@/utils/markdownExport";
|
import { downloadNoteAsMarkdown, downloadAllNotesAsMarkdown, downloadTodosAsMarkdown } from "@/utils/markdownExport";
|
||||||
|
import { TemplateSelector } from "@/components/TemplateSelector";
|
||||||
|
import type { NoteTemplate } from "@/utils/noteTemplates";
|
||||||
|
|
||||||
// ===== FONCTIONS UTILITAIRES TIMEZONE EUROPE/PARIS =====
|
// ===== FONCTIONS UTILITAIRES TIMEZONE EUROPE/PARIS =====
|
||||||
|
|
||||||
@@ -147,6 +149,7 @@ const Index = () => {
|
|||||||
const [addUserModal, setAddUserModal] = useState(false);
|
const [addUserModal, setAddUserModal] = useState(false);
|
||||||
const [deleteUserModal, setDeleteUserModal] = useState<{ open: boolean, userId?: number }>({ open: false });
|
const [deleteUserModal, setDeleteUserModal] = useState<{ open: boolean, userId?: number }>({ open: false });
|
||||||
const [changePasswordModal, setChangePasswordModal] = 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 [addNoteTodoModal, setAddNoteTodoModal] = useState(false);
|
||||||
const [addTagModal, setAddTagModal] = useState(false);
|
const [addTagModal, setAddTagModal] = useState(false);
|
||||||
const [selectedImage, setSelectedImage] = useState<string | null>(null);
|
const [selectedImage, setSelectedImage] = useState<string | null>(null);
|
||||||
@@ -410,12 +413,32 @@ const Index = () => {
|
|||||||
navigate("/login");
|
navigate("/login");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateNote = async () => {
|
const handleCreateNote = () => {
|
||||||
|
setTemplateSelectorOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCreateNoteFromTemplate = async (template: NoteTemplate) => {
|
||||||
try {
|
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) {
|
if (newNote) {
|
||||||
setNotes([newNote, ...notes]);
|
// Add todos if template has any
|
||||||
setOpenNote(newNote);
|
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) {
|
} catch (error) {
|
||||||
showError("Erreur lors de la création de la note");
|
showError("Erreur lors de la création de la note");
|
||||||
@@ -2287,6 +2310,13 @@ const Index = () => {
|
|||||||
confirmText="Ajouter"
|
confirmText="Ajouter"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Template Selector */}
|
||||||
|
<TemplateSelector
|
||||||
|
open={templateSelectorOpen}
|
||||||
|
onOpenChange={setTemplateSelectorOpen}
|
||||||
|
onSelectTemplate={handleCreateNoteFromTemplate}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Add Event Modal */}
|
{/* Add Event Modal */}
|
||||||
<Dialog open={addEventModal} onOpenChange={setAddEventModal}>
|
<Dialog open={addEventModal} onOpenChange={setAddEventModal}>
|
||||||
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
|
||||||
|
|||||||
@@ -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 { Plus, Search, CheckSquare, Image as ImageIcon, Paperclip, Tag as TagIcon, FileText } from "lucide-react";
|
||||||
import NotesService, { Note } from "@/services/NotesService";
|
import NotesService, { Note } from "@/services/NotesService";
|
||||||
import { showError, showSuccess } from "@/utils/toast";
|
import { showError, showSuccess } from "@/utils/toast";
|
||||||
|
import { TemplateSelector } from "@/components/TemplateSelector";
|
||||||
|
import type { NoteTemplate } from "@/utils/noteTemplates";
|
||||||
|
|
||||||
const NOTES_PER_PAGE = 10;
|
const NOTES_PER_PAGE = 10;
|
||||||
|
|
||||||
@@ -16,6 +18,7 @@ export default function NotesPage() {
|
|||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [showArchived, setShowArchived] = useState(false);
|
const [showArchived, setShowArchived] = useState(false);
|
||||||
const [page, setPage] = useState(0);
|
const [page, setPage] = useState(0);
|
||||||
|
const [templateSelectorOpen, setTemplateSelectorOpen] = useState(false);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const loadNotes = async () => {
|
const loadNotes = async () => {
|
||||||
@@ -48,10 +51,24 @@ export default function NotesPage() {
|
|||||||
const totalPages = Math.ceil(filteredNotes.length / NOTES_PER_PAGE);
|
const totalPages = Math.ceil(filteredNotes.length / NOTES_PER_PAGE);
|
||||||
const paginatedNotes = filteredNotes.slice(page * NOTES_PER_PAGE, (page + 1) * 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 {
|
try {
|
||||||
const newNote = await NotesService.createNote("Nouvelle note", "");
|
const title = template.name === "Note vide" ? "Nouvelle note" : template.name;
|
||||||
navigate(`/mobile/notes/${newNote.id}`);
|
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) {
|
} catch (error) {
|
||||||
showError("Erreur lors de la création de la note");
|
showError("Erreur lors de la création de la note");
|
||||||
}
|
}
|
||||||
@@ -238,6 +255,13 @@ export default function NotesPage() {
|
|||||||
|
|
||||||
{/* FAB */}
|
{/* FAB */}
|
||||||
<MobileFAB icon={Plus} onClick={handleCreateNote} label="Créer une note" />
|
<MobileFAB icon={Plus} onClick={handleCreateNote} label="Créer une note" />
|
||||||
|
|
||||||
|
{/* Template Selector */}
|
||||||
|
<TemplateSelector
|
||||||
|
open={templateSelectorOpen}
|
||||||
|
onOpenChange={setTemplateSelectorOpen}
|
||||||
|
onSelectTemplate={handleCreateNoteFromTemplate}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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: `<h2>Réunion - [Titre]</h2>
|
||||||
|
<p><strong>Date:</strong> ${new Date().toLocaleDateString('fr-FR')}</p>
|
||||||
|
<p><strong>Participants:</strong> </p>
|
||||||
|
<p><strong>Objectif:</strong> </p>
|
||||||
|
|
||||||
|
<h3>Points discutés</h3>
|
||||||
|
<p>1. </p>
|
||||||
|
<p>2. </p>
|
||||||
|
<p>3. </p>
|
||||||
|
|
||||||
|
<h3>Décisions</h3>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<h3>Prochaines étapes</h3>
|
||||||
|
<p>→ </p>`,
|
||||||
|
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: `<h2>Journal - ${new Date().toLocaleDateString('fr-FR', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}</h2>
|
||||||
|
|
||||||
|
<h3>🌅 Ce matin je me sens...</h3>
|
||||||
|
<p></p>
|
||||||
|
|
||||||
|
<h3>🎯 Mes priorités aujourd'hui</h3>
|
||||||
|
<p>1. </p>
|
||||||
|
<p>2. </p>
|
||||||
|
<p>3. </p>
|
||||||
|
|
||||||
|
<h3>💡 Idées et réflexions</h3>
|
||||||
|
<p></p>
|
||||||
|
|
||||||
|
<h3>🙏 Gratitude</h3>
|
||||||
|
<p>Aujourd'hui je suis reconnaissant(e) pour:</p>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<h3>🌙 Bilan de la journée</h3>
|
||||||
|
<p></p>`,
|
||||||
|
todos: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'project-planning',
|
||||||
|
name: 'Planification de projet',
|
||||||
|
description: 'Organiser un nouveau projet',
|
||||||
|
icon: '🎯',
|
||||||
|
category: 'Travail',
|
||||||
|
content: `<h2>Projet - [Nom du projet]</h2>
|
||||||
|
|
||||||
|
<h3>📋 Vue d'ensemble</h3>
|
||||||
|
<p><strong>Objectif:</strong> </p>
|
||||||
|
<p><strong>Deadline:</strong> </p>
|
||||||
|
<p><strong>Responsable:</strong> </p>
|
||||||
|
|
||||||
|
<h3>🎯 Objectifs</h3>
|
||||||
|
<p>1. </p>
|
||||||
|
<p>2. </p>
|
||||||
|
<p>3. </p>
|
||||||
|
|
||||||
|
<h3>📊 Phases du projet</h3>
|
||||||
|
<p><strong>Phase 1:</strong> Préparation</p>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<p><strong>Phase 2:</strong> Développement</p>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<p><strong>Phase 3:</strong> Finalisation</p>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<h3>👥 Équipe</h3>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<h3>⚠️ Risques identifiés</h3>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<h3>📌 Ressources nécessaires</h3>
|
||||||
|
<p>• </p>`,
|
||||||
|
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: `<h2>💭 Brainstorming - [Sujet]</h2>
|
||||||
|
|
||||||
|
<p><strong>Date:</strong> ${new Date().toLocaleDateString('fr-FR')}</p>
|
||||||
|
<p><strong>Question centrale:</strong> </p>
|
||||||
|
|
||||||
|
<h3>💡 Idées</h3>
|
||||||
|
<p>1. </p>
|
||||||
|
<p>2. </p>
|
||||||
|
<p>3. </p>
|
||||||
|
<p>4. </p>
|
||||||
|
<p>5. </p>
|
||||||
|
|
||||||
|
<h3>⭐ Idées à creuser</h3>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<h3>🚀 Actions possibles</h3>
|
||||||
|
<p>→ </p>
|
||||||
|
|
||||||
|
<h3>🔗 Connexions et insights</h3>
|
||||||
|
<p></p>`,
|
||||||
|
todos: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'recipe',
|
||||||
|
name: 'Recette de cuisine',
|
||||||
|
description: 'Noter une recette',
|
||||||
|
icon: '👨🍳',
|
||||||
|
category: 'Personnel',
|
||||||
|
content: `<h2>🍳 Recette - [Nom du plat]</h2>
|
||||||
|
|
||||||
|
<p><strong>⏱️ Temps de préparation:</strong> </p>
|
||||||
|
<p><strong>👥 Portions:</strong> </p>
|
||||||
|
<p><strong>😋 Difficulté:</strong> Facile / Moyen / Difficile</p>
|
||||||
|
|
||||||
|
<h3>📝 Ingrédients</h3>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<h3>👨🍳 Préparation</h3>
|
||||||
|
<p>1. </p>
|
||||||
|
<p>2. </p>
|
||||||
|
<p>3. </p>
|
||||||
|
|
||||||
|
<h3>💡 Astuces</h3>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<h3>📌 Notes</h3>
|
||||||
|
<p></p>`,
|
||||||
|
todos: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'weekly-review',
|
||||||
|
name: 'Revue hebdomadaire',
|
||||||
|
description: 'Bilan et planification de la semaine',
|
||||||
|
icon: '📊',
|
||||||
|
category: 'Productivité',
|
||||||
|
content: `<h2>📊 Revue de la semaine</h2>
|
||||||
|
<p><strong>Semaine du:</strong> ${new Date().toLocaleDateString('fr-FR')}</p>
|
||||||
|
|
||||||
|
<h3>✅ Accomplissements</h3>
|
||||||
|
<p>Cette semaine j'ai réussi à:</p>
|
||||||
|
<p>1. </p>
|
||||||
|
<p>2. </p>
|
||||||
|
<p>3. </p>
|
||||||
|
|
||||||
|
<h3>📈 Progrès</h3>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<h3>⚠️ Défis rencontrés</h3>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<h3>💡 Leçons apprises</h3>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<h3>🎯 Objectifs semaine prochaine</h3>
|
||||||
|
<p>1. </p>
|
||||||
|
<p>2. </p>
|
||||||
|
<p>3. </p>
|
||||||
|
|
||||||
|
<h3>🔄 Habitudes à améliorer</h3>
|
||||||
|
<p>• </p>`,
|
||||||
|
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: `<h2>🐛 Bug Report</h2>
|
||||||
|
|
||||||
|
<p><strong>Date:</strong> ${new Date().toLocaleDateString('fr-FR')}</p>
|
||||||
|
<p><strong>Priorité:</strong> 🔴 Critique / 🟠 Haute / 🟡 Moyenne / 🟢 Basse</p>
|
||||||
|
<p><strong>Statut:</strong> Nouveau / En cours / Résolu</p>
|
||||||
|
|
||||||
|
<h3>📋 Description</h3>
|
||||||
|
<p></p>
|
||||||
|
|
||||||
|
<h3>🔄 Étapes pour reproduire</h3>
|
||||||
|
<p>1. </p>
|
||||||
|
<p>2. </p>
|
||||||
|
<p>3. </p>
|
||||||
|
|
||||||
|
<h3>✅ Résultat attendu</h3>
|
||||||
|
<p></p>
|
||||||
|
|
||||||
|
<h3>❌ Résultat actuel</h3>
|
||||||
|
<p></p>
|
||||||
|
|
||||||
|
<h3>💻 Environnement</h3>
|
||||||
|
<p><strong>OS:</strong> </p>
|
||||||
|
<p><strong>Navigateur:</strong> </p>
|
||||||
|
<p><strong>Version:</strong> </p>
|
||||||
|
|
||||||
|
<h3>📌 Notes additionnelles</h3>
|
||||||
|
<p></p>
|
||||||
|
|
||||||
|
<h3>🔧 Solution proposée</h3>
|
||||||
|
<p></p>`,
|
||||||
|
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: `<h2>📚 Notes d'apprentissage - [Sujet]</h2>
|
||||||
|
|
||||||
|
<p><strong>Date:</strong> ${new Date().toLocaleDateString('fr-FR')}</p>
|
||||||
|
<p><strong>Source:</strong> </p>
|
||||||
|
|
||||||
|
<h3>🎯 Objectifs d'apprentissage</h3>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<h3>📝 Concepts clés</h3>
|
||||||
|
<p><strong>Concept 1:</strong> </p>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<p><strong>Concept 2:</strong> </p>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<h3>💡 Exemples</h3>
|
||||||
|
<p></p>
|
||||||
|
|
||||||
|
<h3>❓ Questions</h3>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<h3>🔗 Ressources</h3>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<h3>📌 À retenir</h3>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<h3>✍️ Pratique</h3>
|
||||||
|
<p>Exercices à faire:</p>
|
||||||
|
<p>1. </p>`,
|
||||||
|
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: `<h2>✈️ Voyage - [Destination]</h2>
|
||||||
|
|
||||||
|
<p><strong>📅 Dates:</strong> du [date] au [date]</p>
|
||||||
|
<p><strong>👥 Voyageurs:</strong> </p>
|
||||||
|
<p><strong>💰 Budget:</strong> </p>
|
||||||
|
|
||||||
|
<h3>🎒 À emporter</h3>
|
||||||
|
<p>Vêtements:</p>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<p>Documents:</p>
|
||||||
|
<p>• Passeport</p>
|
||||||
|
<p>• Billets</p>
|
||||||
|
<p>• Assurance</p>
|
||||||
|
|
||||||
|
<h3>🏨 Hébergement</h3>
|
||||||
|
<p><strong>Hôtel/Airbnb:</strong> </p>
|
||||||
|
<p><strong>Adresse:</strong> </p>
|
||||||
|
<p><strong>Check-in:</strong> </p>
|
||||||
|
|
||||||
|
<h3>✈️ Transport</h3>
|
||||||
|
<p><strong>Vol aller:</strong> </p>
|
||||||
|
<p><strong>Vol retour:</strong> </p>
|
||||||
|
|
||||||
|
<h3>📍 Lieux à visiter</h3>
|
||||||
|
<p>1. </p>
|
||||||
|
<p>2. </p>
|
||||||
|
<p>3. </p>
|
||||||
|
|
||||||
|
<h3>🍽️ Restaurants recommandés</h3>
|
||||||
|
<p>• </p>
|
||||||
|
|
||||||
|
<h3>💡 Conseils et infos pratiques</h3>
|
||||||
|
<p>• </p>`,
|
||||||
|
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<string, NoteTemplate[]> {
|
||||||
|
const categorized = new Map<string, NoteTemplate[]>();
|
||||||
|
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user