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 = `
+
+
+ `;
+
+ 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