From b8a19a25bf206f9cd88a5813d157769bf5097e5d Mon Sep 17 00:00:00 2001 From: "[dyad]" Date: Thu, 6 Nov 2025 13:36:43 +0100 Subject: [PATCH] [dyad] wrote 3 file(s) --- public/js/admin.js | 139 ++++++++++++++++++++++ public/js/app.js | 21 ++++ public/js/notes.js | 289 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 449 insertions(+) create mode 100644 public/js/admin.js create mode 100644 public/js/app.js create mode 100644 public/js/notes.js diff --git a/public/js/admin.js b/public/js/admin.js new file mode 100644 index 0000000..cfc8aa2 --- /dev/null +++ b/public/js/admin.js @@ -0,0 +1,139 @@ +class AdminService { + constructor() { + if (!window.auth.user?.is_admin) return; + + this.adminPanel = document.getElementById('admin-panel'); + this.closeAdminBtn = document.getElementById('close-admin-btn'); + this.addUserBtn = document.getElementById('add-user-btn'); + this.usersList = document.getElementById('users-list'); + + this.setupEventListeners(); + } + + setupEventListeners() { + this.closeAdminBtn.addEventListener('click', () => this.closePanel()); + this.addUserBtn.addEventListener('click', () => this.showAddUserForm()); + } + + async loadUsers() { + try { + const response = await fetch('/api/users', { + headers: window.auth.getHeaders() + }); + const users = await response.json(); + this.renderUsersList(users); + } catch (error) { + console.error('Error loading users:', error); + } + } + + renderUsersList(users) { + this.usersList.innerHTML = ''; + users.forEach(user => { + const userElement = document.createElement('div'); + userElement.className = 'user-item'; + userElement.innerHTML = ` +
+ ${user.username} + ${user.is_admin ? 'Admin' : ''} +
+
+ ${user.id !== 1 ? ` + + + + ` : ''} +
+ `; + this.usersList.appendChild(userElement); + }); + lucide.createIcons(); + } + + showAddUserForm() { + const username = prompt('Nom d\'utilisateur:'); + if (!username) return; + + const password = prompt('Mot de passe:'); + if (!password) return; + + const isAdmin = confirm('Donner les droits administrateur ?'); + + this.createUser(username, password, isAdmin); + } + + async createUser(username, password, isAdmin) { + try { + const response = await fetch('/api/users', { + method: 'POST', + headers: window.auth.getHeaders(), + body: JSON.stringify({ username, password, is_admin: isAdmin }) + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.message); + } + + this.loadUsers(); + } catch (error) { + alert(error.message); + } + } + + async changePassword(userId) { + const password = prompt('Nouveau mot de passe:'); + if (!password) return; + + try { + await fetch(`/api/users/${userId}`, { + method: 'PUT', + headers: window.auth.getHeaders(), + body: JSON.stringify({ password }) + }); + alert('Mot de passe modifié'); + } catch (error) { + console.error('Error changing password:', error); + } + } + + async toggleAdmin(userId, currentStatus) { + try { + await fetch(`/api/users/${userId}`, { + method: 'PUT', + headers: window.auth.getHeaders(), + body: JSON.stringify({ is_admin: !currentStatus }) + }); + this.loadUsers(); + } catch (error) { + console.error('Error toggling admin status:', error); + } + } + + async deleteUser(userId) { + if (!confirm('Êtes-vous sûr de vouloir supprimer cet utilisateur ?')) return; + + try { + await fetch(`/api/users/${userId}`, { + method: 'DELETE', + headers: window.auth.getHeaders() + }); + this.loadUsers(); + } catch (error) { + console.error('Error deleting user:', error); + } + } + + closePanel() { + this.adminPanel.classList.add('hidden'); + } +} + +// Initialize admin service +window.admin = new AdminService(); \ No newline at end of file diff --git a/public/js/app.js b/public/js/app.js new file mode 100644 index 0000000..f765897 --- /dev/null +++ b/public/js/app.js @@ -0,0 +1,21 @@ +class App { + constructor() { + // Initialize app components + this.auth = window.auth; + this.notes = window.notes; + this.admin = window.admin; + } + + init() { + // Load initial data if authenticated + if (this.auth.token) { + this.notes.loadNotes(); + if (this.auth.user.is_admin) { + this.admin.loadUsers(); + } + } + } +} + +// Initialize main app +window.app = new App(); \ No newline at end of file diff --git a/public/js/notes.js b/public/js/notes.js new file mode 100644 index 0000000..42512ae --- /dev/null +++ b/public/js/notes.js @@ -0,0 +1,289 @@ +class NotesService { + constructor() { + this.currentNote = null; + this.notes = []; + + // DOM Elements + this.notesList = document.getElementById('notes-list'); + this.noteEditor = document.getElementById('note-editor'); + this.noteTitle = document.getElementById('note-title'); + this.noteContent = document.getElementById('note-content'); + this.todosList = document.getElementById('todos-list'); + this.imagesList = document.getElementById('images-list'); + this.searchInput = document.getElementById('search-notes'); + this.newNoteBtn = document.getElementById('new-note-btn'); + this.archiveNoteBtn = document.getElementById('archive-note-btn'); + this.deleteNoteBtn = document.getElementById('delete-note-btn'); + this.addTodoBtn = document.getElementById('add-todo-btn'); + this.imageUpload = document.getElementById('image-upload'); + + this.setupEventListeners(); + this.loadNotes(); + } + + setupEventListeners() { + this.newNoteBtn.addEventListener('click', () => this.createNewNote()); + this.archiveNoteBtn.addEventListener('click', () => this.toggleArchiveNote()); + this.deleteNoteBtn.addEventListener('click', () => this.deleteNote()); + this.addTodoBtn.addEventListener('click', () => this.addTodo()); + this.imageUpload.addEventListener('change', (e) => this.handleImageUpload(e)); + this.searchInput.addEventListener('input', (e) => this.handleSearch(e)); + + // Auto-save on content changes + this.noteTitle.addEventListener('input', () => this.autoSave()); + this.noteContent.addEventListener('input', () => this.autoSave()); + + // Initialize Lucide icons + lucide.createIcons(); + } + + async loadNotes() { + try { + const response = await fetch('/api/notes', { + headers: window.auth.getHeaders() + }); + this.notes = await response.json(); + this.renderNotesList(); + } catch (error) { + console.error('Error loading notes:', error); + } + } + + renderNotesList(searchTerm = '') { + this.notesList.innerHTML = ''; + + const filteredNotes = searchTerm + ? this.notes.filter(note => + note.title.toLowerCase().includes(searchTerm.toLowerCase()) || + note.content.toLowerCase().includes(searchTerm.toLowerCase()) + ) + : this.notes; + + filteredNotes.forEach(note => { + const noteElement = document.createElement('div'); + noteElement.className = `note-item ${note.id === this.currentNote?.id ? 'active' : ''}`; + noteElement.innerHTML = ` +

${note.title || 'Sans titre'}

+

${note.content || ''}

+ `; + noteElement.addEventListener('click', () => this.selectNote(note)); + this.notesList.appendChild(noteElement); + }); + } + + selectNote(note) { + this.currentNote = note; + this.noteTitle.value = note.title; + this.noteContent.innerHTML = note.content; + this.renderTodos(); + this.renderImages(); + this.renderNotesList(); // Update active state + } + + renderTodos() { + this.todosList.innerHTML = ''; + this.currentNote.todos.forEach((todo, index) => { + const todoElement = document.createElement('div'); + todoElement.className = `todo-item ${todo.completed ? 'completed' : ''}`; + todoElement.innerHTML = ` + + ${todo.text} + + `; + + const checkbox = todoElement.querySelector('input'); + checkbox.addEventListener('change', () => this.toggleTodo(index)); + + const deleteBtn = todoElement.querySelector('button'); + deleteBtn.addEventListener('click', () => this.deleteTodo(index)); + + this.todosList.appendChild(todoElement); + }); + lucide.createIcons(); + } + + renderImages() { + this.imagesList.innerHTML = ''; + this.currentNote.images.forEach((image, index) => { + const imageElement = document.createElement('div'); + imageElement.className = 'image-thumbnail'; + imageElement.innerHTML = ` + Note image + + `; + + const deleteBtn = imageElement.querySelector('button'); + deleteBtn.addEventListener('click', () => this.deleteImage(image.id)); + + this.imagesList.appendChild(imageElement); + }); + lucide.createIcons(); + } + + async createNewNote() { + try { + const response = await fetch('/api/notes', { + method: 'POST', + headers: window.auth.getHeaders(), + body: JSON.stringify({ + title: 'Nouvelle note', + content: '', + todos: [] + }) + }); + + const newNote = await response.json(); + this.notes.unshift(newNote); + this.renderNotesList(); + this.selectNote(newNote); + } catch (error) { + console.error('Error creating note:', error); + } + } + + async autoSave() { + if (!this.currentNote) return; + + clearTimeout(this.saveTimeout); + this.saveTimeout = setTimeout(async () => { + try { + await fetch(`/api/notes/${this.currentNote.id}`, { + method: 'PUT', + headers: window.auth.getHeaders(), + body: JSON.stringify({ + title: this.noteTitle.value, + content: this.noteContent.innerHTML, + todos: this.currentNote.todos + }) + }); + + // Update local note + this.currentNote.title = this.noteTitle.value; + this.currentNote.content = this.noteContent.innerHTML; + this.renderNotesList(); + } catch (error) { + console.error('Error saving note:', error); + } + }, 1000); + } + + async toggleArchiveNote() { + if (!this.currentNote) return; + + try { + await fetch(`/api/notes/${this.currentNote.id}`, { + method: 'PUT', + headers: window.auth.getHeaders(), + body: JSON.stringify({ + ...this.currentNote, + archived: !this.currentNote.archived + }) + }); + + this.currentNote.archived = !this.currentNote.archived; + this.renderNotesList(); + } catch (error) { + console.error('Error archiving note:', error); + } + } + + async deleteNote() { + if (!this.currentNote || !confirm('Êtes-vous sûr de vouloir supprimer cette note ?')) return; + + try { + await fetch(`/api/notes/${this.currentNote.id}`, { + method: 'DELETE', + headers: window.auth.getHeaders() + }); + + const index = this.notes.findIndex(n => n.id === this.currentNote.id); + this.notes.splice(index, 1); + this.currentNote = null; + this.renderNotesList(); + this.noteTitle.value = ''; + this.noteContent.innerHTML = ''; + this.todosList.innerHTML = ''; + this.imagesList.innerHTML = ''; + } catch (error) { + console.error('Error deleting note:', error); + } + } + + async addTodo() { + if (!this.currentNote) return; + + const text = prompt('Nouveau todo:'); + if (!text) return; + + this.currentNote.todos.push({ text, completed: false }); + this.renderTodos(); + this.autoSave(); + } + + async toggleTodo(index) { + if (!this.currentNote) return; + + this.currentNote.todos[index].completed = !this.currentNote.todos[index].completed; + this.renderTodos(); + this.autoSave(); + } + + async deleteTodo(index) { + if (!this.currentNote) return; + + this.currentNote.todos.splice(index, 1); + this.renderTodos(); + this.autoSave(); + } + + async handleImageUpload(event) { + if (!this.currentNote || !event.target.files.length) return; + + const formData = new FormData(); + formData.append('image', event.target.files[0]); + + try { + const response = await fetch(`/api/notes/${this.currentNote.id}/images`, { + method: 'POST', + headers: { 'Authorization': window.auth.getHeaders().Authorization }, + body: formData + }); + + const image = await response.json(); + this.currentNote.images.push(image); + this.renderImages(); + } catch (error) { + console.error('Error uploading image:', error); + } + + event.target.value = ''; // Reset input + } + + async deleteImage(imageId) { + if (!this.currentNote || !confirm('Supprimer cette image ?')) return; + + try { + await fetch(`/api/notes/${this.currentNote.id}/images/${imageId}`, { + method: 'DELETE', + headers: window.auth.getHeaders() + }); + + const index = this.currentNote.images.findIndex(img => img.id === imageId); + this.currentNote.images.splice(index, 1); + this.renderImages(); + } catch (error) { + console.error('Error deleting image:', error); + } + } + + handleSearch(event) { + this.renderNotesList(event.target.value); + } +} + +// Initialize notes service +window.notes = new NotesService(); \ No newline at end of file