[dyad] wrote 4 file(s)

This commit is contained in:
Michael committed 2025-11-06 16:01:23 +01:00
1 parent 0bcffed331
commit 11dca3824c
4 files changed
+121 -52

No files matched your search

+34 -11
View File
@@ -1,21 +1,30 @@
class AdminService { class AdminService {
constructor() { constructor() {
if (!window.auth.user?.is_admin) return; // Check if auth exists and user is admin
if (!window.auth || !window.auth.user || !window.auth.user.is_admin) return;
this.adminPanel = document.getElementById('admin-panel'); this.adminPanel = document.getElementById('admin-panel');
this.closeAdminBtn = document.getElementById('close-admin-btn'); this.closeAdminBtn = document.getElementById('close-admin-btn');
this.addUserBtn = document.getElementById('add-user-btn'); this.addUserBtn = document.getElementById('add-user-btn');
this.usersList = document.getElementById('users-list'); this.usersList = document.getElementById('users-list');
this.setupEventListeners(); if (this.adminPanel && this.closeAdminBtn && this.addUserBtn && this.usersList) {
this.setupEventListeners();
}
} }
setupEventListeners() { setupEventListeners() {
this.closeAdminBtn.addEventListener('click', () => this.closePanel()); if (this.closeAdminBtn) {
this.addUserBtn.addEventListener('click', () => this.showAddUserForm()); this.closeAdminBtn.addEventListener('click', () => this.closePanel());
}
if (this.addUserBtn) {
this.addUserBtn.addEventListener('click', () => this.showAddUserForm());
}
} }
async loadUsers() { async loadUsers() {
if (!window.auth || !window.auth.getHeaders) return;
try { try {
const response = await fetch('/api/users', { const response = await fetch('/api/users', {
headers: window.auth.getHeaders() headers: window.auth.getHeaders()
@@ -28,24 +37,28 @@ class AdminService {
} }
renderUsersList(users) { renderUsersList(users) {
if (!this.usersList || !Array.isArray(users)) return;
this.usersList.innerHTML = ''; this.usersList.innerHTML = '';
users.forEach(user => { users.forEach(user => {
if (!user || typeof user !== 'object') return;
const userElement = document.createElement('div'); const userElement = document.createElement('div');
userElement.className = 'user-item'; userElement.className = 'user-item';
userElement.innerHTML = ` userElement.innerHTML = `
<div> <div>
<span class="font-medium">${user.username}</span> <span class="font-medium">${user.username || 'Utilisateur'}</span>
${user.is_admin ? '<span class="ml-2 text-sm text-blue-600">Admin</span>' : ''} ${user.is_admin ? '<span class="ml-2 text-sm text-blue-600">Admin</span>' : ''}
</div> </div>
<div class="actions"> <div class="actions">
${user.id !== 1 ? ` ${user.id !== 1 ? `
<button class="text-gray-600 hover:text-blue-600" onclick="window.admin.changePassword(${user.id})"> <button class="text-gray-600 hover:text-blue-600" onclick="if(window.admin && typeof window.admin.changePassword === 'function') window.admin.changePassword(${user.id})">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"></path></svg>
</button> </button>
<button class="text-gray-600 hover:text-blue-600" onclick="window.admin.toggleAdmin(${user.id}, ${user.is_admin})"> <button class="text-gray-600 hover:text-blue-600" onclick="if(window.admin && typeof window.admin.toggleAdmin === 'function') window.admin.toggleAdmin(${user.id}, ${user.is_admin ? 'true' : 'false'})">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
</button> </button>
<button class="text-gray-600 hover:text-red-600" onclick="window.admin.deleteUser(${user.id})"> <button class="text-gray-600 hover:text-red-600" onclick="if(window.admin && typeof window.admin.deleteUser === 'function') window.admin.deleteUser(${user.id})">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>
</button> </button>
` : ''} ` : ''}
@@ -68,6 +81,8 @@ class AdminService {
} }
async createUser(username, password, isAdmin) { async createUser(username, password, isAdmin) {
if (!window.auth || !window.auth.getHeaders) return;
try { try {
const response = await fetch('/api/users', { const response = await fetch('/api/users', {
method: 'POST', method: 'POST',
@@ -77,16 +92,18 @@ class AdminService {
if (!response.ok) { if (!response.ok) {
const error = await response.json(); const error = await response.json();
throw new Error(error.message); throw new Error(error.message || 'Erreur lors de la création de l\'utilisateur');
} }
this.loadUsers(); this.loadUsers();
} catch (error) { } catch (error) {
alert(error.message); alert(error.message || 'Erreur lors de la création de l\'utilisateur');
} }
} }
async changePassword(userId) { async changePassword(userId) {
if (!window.auth || !window.auth.getHeaders) return;
const password = prompt('Nouveau mot de passe:'); const password = prompt('Nouveau mot de passe:');
if (!password) return; if (!password) return;
@@ -103,6 +120,8 @@ class AdminService {
} }
async toggleAdmin(userId, currentStatus) { async toggleAdmin(userId, currentStatus) {
if (!window.auth || !window.auth.getHeaders) return;
try { try {
await fetch(`/api/users/${userId}`, { await fetch(`/api/users/${userId}`, {
method: 'PUT', method: 'PUT',
@@ -116,6 +135,8 @@ class AdminService {
} }
async deleteUser(userId) { async deleteUser(userId) {
if (!window.auth || !window.auth.getHeaders) return;
if (!confirm('Êtes-vous sûr de vouloir supprimer cet utilisateur ?')) return; if (!confirm('Êtes-vous sûr de vouloir supprimer cet utilisateur ?')) return;
try { try {
@@ -130,7 +151,9 @@ class AdminService {
} }
closePanel() { closePanel() {
this.adminPanel.classList.add('hidden'); if (this.adminPanel) {
this.adminPanel.classList.add('hidden');
}
} }
} }
+9 -4
View File
@@ -8,9 +8,12 @@ class App {
init() { init() {
// Load initial data if authenticated // Load initial data if authenticated
if (this.auth.token) { if (this.auth && this.auth.token) {
this.notes.loadNotes(); if (this.notes && typeof this.notes.loadNotes === 'function') {
if (this.auth.user && this.auth.user.is_admin) { this.notes.loadNotes();
}
if (this.auth.user && this.auth.user.is_admin && this.admin && typeof this.admin.loadUsers === 'function') {
this.admin.loadUsers(); this.admin.loadUsers();
} }
} }
@@ -22,5 +25,7 @@ window.app = new App();
// Initialize app when DOM is loaded // Initialize app when DOM is loaded
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
window.app.init(); if (window.app && typeof window.app.init === 'function') {
window.app.init();
}
}); });
+13 -9
View File
@@ -43,7 +43,9 @@ class AuthService {
localStorage.setItem('user', JSON.stringify(this.user)); localStorage.setItem('user', JSON.stringify(this.user));
this.updateUI(); this.updateUI();
window.app.init(); // Initialize main app after login if (window.app && typeof window.app.init === 'function') {
window.app.init(); // Initialize main app after login
}
} catch (error) { } catch (error) {
alert(error.message); alert(error.message);
} }
@@ -64,30 +66,32 @@ class AuthService {
const adminPanel = document.getElementById('admin-panel'); const adminPanel = document.getElementById('admin-panel');
if (this.token && this.user) { if (this.token && this.user) {
authContainer.classList.add('hidden'); if (authContainer) authContainer.classList.add('hidden');
appContainer.classList.remove('hidden'); if (appContainer) appContainer.classList.remove('hidden');
this.userInfo.textContent = this.user.username; if (this.userInfo) this.userInfo.textContent = this.user.username;
// Show admin panel button if user is admin // Show admin panel button if user is admin
if (this.user.is_admin) { if (this.user.is_admin) {
const adminBtnExists = document.querySelector('.admin-btn'); const adminBtnExists = document.querySelector('.admin-btn');
if (!adminBtnExists) { if (!adminBtnExists && this.userInfo && this.userInfo.parentNode) {
const adminBtn = document.createElement('button'); const adminBtn = document.createElement('button');
adminBtn.className = 'admin-btn ml-4 text-sm text-gray-600 hover:text-gray-900'; adminBtn.className = 'admin-btn ml-4 text-sm text-gray-600 hover:text-gray-900';
adminBtn.textContent = 'Admin'; adminBtn.textContent = 'Admin';
adminBtn.onclick = () => adminPanel.classList.remove('hidden'); adminBtn.onclick = () => {
if (adminPanel) adminPanel.classList.remove('hidden');
};
this.userInfo.parentNode.insertBefore(adminBtn, this.logoutBtn); this.userInfo.parentNode.insertBefore(adminBtn, this.logoutBtn);
} }
} }
} else { } else {
authContainer.classList.remove('hidden'); if (authContainer) authContainer.classList.remove('hidden');
appContainer.classList.add('hidden'); if (appContainer) appContainer.classList.add('hidden');
} }
} }
getHeaders() { getHeaders() {
return { return {
'Authorization': `Bearer ${this.token}`, 'Authorization': `Bearer ${this.token || ''}`,
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}; };
} }
+65 -28
View File
@@ -50,7 +50,7 @@ class NotesService {
} }
async loadNotes() { async loadNotes() {
if (!window.auth.token) { if (!window.auth || !window.auth.token) {
console.log('Not authenticated, skipping notes loading'); console.log('Not authenticated, skipping notes loading');
return; return;
} }
@@ -83,14 +83,16 @@ class NotesService {
const filteredNotes = searchTerm const filteredNotes = searchTerm
? this.notes.filter(note => ? this.notes.filter(note =>
note.title.toLowerCase().includes(searchTerm.toLowerCase()) || (note.title && note.title.toLowerCase().includes(searchTerm.toLowerCase())) ||
(note.content && note.content.toLowerCase().includes(searchTerm.toLowerCase())) (note.content && note.content.toLowerCase().includes(searchTerm.toLowerCase()))
) )
: this.notes; : this.notes;
filteredNotes.forEach(note => { filteredNotes.forEach(note => {
if (!note) return;
const noteElement = document.createElement('div'); const noteElement = document.createElement('div');
noteElement.className = `note-item ${note.id === this.currentNote?.id ? 'active' : ''}`; noteElement.className = `note-item ${note.id === (this.currentNote && this.currentNote.id) ? 'active' : ''}`;
noteElement.innerHTML = ` noteElement.innerHTML = `
<h3 class="font-medium">${note.title || 'Sans titre'}</h3> <h3 class="font-medium">${note.title || 'Sans titre'}</h3>
<p class="text-sm text-gray-500 truncate">${note.content || ''}</p> <p class="text-sm text-gray-500 truncate">${note.content || ''}</p>
@@ -101,6 +103,8 @@ class NotesService {
} }
selectNote(note) { selectNote(note) {
if (!note) return;
this.currentNote = note; this.currentNote = note;
if (this.noteTitle) this.noteTitle.value = note.title || ''; if (this.noteTitle) this.noteTitle.value = note.title || '';
if (this.noteContent) this.noteContent.innerHTML = note.content || ''; if (this.noteContent) this.noteContent.innerHTML = note.content || '';
@@ -110,35 +114,47 @@ class NotesService {
} }
renderTodos() { renderTodos() {
if (!this.todosList || !this.currentNote || !this.currentNote.todos) return; if (!this.todosList || !this.currentNote) return;
const todos = this.currentNote.todos || [];
this.todosList.innerHTML = ''; this.todosList.innerHTML = '';
this.currentNote.todos.forEach((todo, index) => { todos.forEach((todo, index) => {
if (!todo) return;
const todoElement = document.createElement('div'); const todoElement = document.createElement('div');
todoElement.className = `todo-item ${todo.completed ? 'completed' : ''}`; todoElement.className = `todo-item ${todo.completed ? 'completed' : ''}`;
todoElement.innerHTML = ` todoElement.innerHTML = `
<input type="checkbox" ${todo.completed ? 'checked' : ''}> <input type="checkbox" ${todo.completed ? 'checked' : ''}>
<span>${todo.text}</span> <span>${todo.text || ''}</span>
<button class="ml-auto text-gray-400 hover:text-red-500"> <button class="ml-auto text-gray-400 hover:text-red-500">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</button> </button>
`; `;
const checkbox = todoElement.querySelector('input'); const checkbox = todoElement.querySelector('input');
checkbox.addEventListener('change', () => this.toggleTodo(index)); if (checkbox) {
checkbox.addEventListener('change', () => this.toggleTodo(index));
}
const deleteBtn = todoElement.querySelector('button'); const deleteBtn = todoElement.querySelector('button');
deleteBtn.addEventListener('click', () => this.deleteTodo(index)); if (deleteBtn) {
deleteBtn.addEventListener('click', () => this.deleteTodo(index));
}
this.todosList.appendChild(todoElement); this.todosList.appendChild(todoElement);
}); });
} }
renderImages() { renderImages() {
if (!this.imagesList || !this.currentNote || !this.currentNote.images) return; if (!this.imagesList || !this.currentNote) return;
const images = this.currentNote.images || [];
this.imagesList.innerHTML = ''; this.imagesList.innerHTML = '';
this.currentNote.images.forEach((image) => { images.forEach((image, index) => {
if (!image || !image.filename) return;
const imageElement = document.createElement('div'); const imageElement = document.createElement('div');
imageElement.className = 'image-thumbnail'; imageElement.className = 'image-thumbnail';
imageElement.innerHTML = ` imageElement.innerHTML = `
@@ -149,13 +165,17 @@ class NotesService {
`; `;
const deleteBtn = imageElement.querySelector('button'); const deleteBtn = imageElement.querySelector('button');
deleteBtn.addEventListener('click', () => this.deleteImage(image.id)); if (deleteBtn) {
deleteBtn.addEventListener('click', () => this.deleteImage(image.id));
}
this.imagesList.appendChild(imageElement); this.imagesList.appendChild(imageElement);
}); });
} }
async createNewNote() { async createNewNote() {
if (!window.auth || !window.auth.getHeaders) return;
try { try {
const response = await fetch('/api/notes', { const response = await fetch('/api/notes', {
method: 'POST', method: 'POST',
@@ -168,33 +188,37 @@ class NotesService {
}); });
const newNote = await response.json(); const newNote = await response.json();
this.notes.unshift(newNote); if (newNote) {
this.renderNotesList(); this.notes.unshift(newNote);
this.selectNote(newNote); this.renderNotesList();
this.selectNote(newNote);
}
} catch (error) { } catch (error) {
console.error('Error creating note:', error); console.error('Error creating note:', error);
} }
} }
async autoSave() { async autoSave() {
if (!this.currentNote) return; if (!this.currentNote || !window.auth || !window.auth.getHeaders) return;
clearTimeout(this.saveTimeout); clearTimeout(this.saveTimeout);
this.saveTimeout = setTimeout(async () => { this.saveTimeout = setTimeout(async () => {
try { try {
if (!this.noteTitle || !this.noteContent) return;
await fetch(`/api/notes/${this.currentNote.id}`, { await fetch(`/api/notes/${this.currentNote.id}`, {
method: 'PUT', method: 'PUT',
headers: window.auth.getHeaders(), headers: window.auth.getHeaders(),
body: JSON.stringify({ body: JSON.stringify({
title: this.noteTitle.value, title: this.noteTitle.value || '',
content: this.noteContent.innerHTML, content: this.noteContent.innerHTML || '',
todos: this.currentNote.todos todos: this.currentNote.todos || []
}) })
}); });
// Update local note // Update local note
this.currentNote.title = this.noteTitle.value; this.currentNote.title = this.noteTitle.value || '';
this.currentNote.content = this.noteContent.innerHTML; this.currentNote.content = this.noteContent.innerHTML || '';
this.renderNotesList(); this.renderNotesList();
} catch (error) { } catch (error) {
console.error('Error saving note:', error); console.error('Error saving note:', error);
@@ -203,7 +227,7 @@ class NotesService {
} }
async toggleArchiveNote() { async toggleArchiveNote() {
if (!this.currentNote) return; if (!this.currentNote || !window.auth || !window.auth.getHeaders) return;
try { try {
await fetch(`/api/notes/${this.currentNote.id}`, { await fetch(`/api/notes/${this.currentNote.id}`, {
@@ -223,7 +247,8 @@ class NotesService {
} }
async deleteNote() { async deleteNote() {
if (!this.currentNote || !confirm('Êtes-vous sûr de vouloir supprimer cette note ?')) return; if (!this.currentNote || !window.auth || !window.auth.getHeaders) return;
if (!confirm('Êtes-vous sûr de vouloir supprimer cette note ?')) return;
try { try {
await fetch(`/api/notes/${this.currentNote.id}`, { await fetch(`/api/notes/${this.currentNote.id}`, {
@@ -232,7 +257,9 @@ class NotesService {
}); });
const index = this.notes.findIndex(n => n.id === this.currentNote.id); const index = this.notes.findIndex(n => n.id === this.currentNote.id);
this.notes.splice(index, 1); if (index !== -1) {
this.notes.splice(index, 1);
}
this.currentNote = null; this.currentNote = null;
this.renderNotesList(); this.renderNotesList();
if (this.noteTitle) this.noteTitle.value = ''; if (this.noteTitle) this.noteTitle.value = '';
@@ -261,6 +288,7 @@ class NotesService {
async toggleTodo(index) { async toggleTodo(index) {
if (!this.currentNote || !this.currentNote.todos) return; if (!this.currentNote || !this.currentNote.todos) return;
if (index < 0 || index >= this.currentNote.todos.length) return;
this.currentNote.todos[index].completed = !this.currentNote.todos[index].completed; this.currentNote.todos[index].completed = !this.currentNote.todos[index].completed;
this.renderTodos(); this.renderTodos();
@@ -269,6 +297,7 @@ class NotesService {
async deleteTodo(index) { async deleteTodo(index) {
if (!this.currentNote || !this.currentNote.todos) return; if (!this.currentNote || !this.currentNote.todos) return;
if (index < 0 || index >= this.currentNote.todos.length) return;
this.currentNote.todos.splice(index, 1); this.currentNote.todos.splice(index, 1);
this.renderTodos(); this.renderTodos();
@@ -276,7 +305,8 @@ class NotesService {
} }
async handleImageUpload(event) { async handleImageUpload(event) {
if (!this.currentNote || !event.target.files.length) return; if (!this.currentNote || !event.target || !event.target.files || !event.target.files.length) return;
if (!window.auth || !window.auth.getHeaders) return;
const formData = new FormData(); const formData = new FormData();
formData.append('image', event.target.files[0]); formData.append('image', event.target.files[0]);
@@ -298,11 +328,15 @@ class NotesService {
console.error('Error uploading image:', error); console.error('Error uploading image:', error);
} }
event.target.value = ''; // Reset input if (event.target) {
event.target.value = ''; // Reset input
}
} }
async deleteImage(imageId) { async deleteImage(imageId) {
if (!this.currentNote || !this.currentNote.images || !confirm('Supprimer cette image ?')) return; if (!this.currentNote || !this.currentNote.images) return;
if (!window.auth || !window.auth.getHeaders) return;
if (!confirm('Supprimer cette image ?')) return;
try { try {
await fetch(`/api/notes/${this.currentNote.id}/images/${imageId}`, { await fetch(`/api/notes/${this.currentNote.id}/images/${imageId}`, {
@@ -311,7 +345,9 @@ class NotesService {
}); });
const index = this.currentNote.images.findIndex(img => img.id === imageId); const index = this.currentNote.images.findIndex(img => img.id === imageId);
this.currentNote.images.splice(index, 1); if (index !== -1) {
this.currentNote.images.splice(index, 1);
}
this.renderImages(); this.renderImages();
} catch (error) { } catch (error) {
console.error('Error deleting image:', error); console.error('Error deleting image:', error);
@@ -319,7 +355,8 @@ class NotesService {
} }
handleSearch(event) { handleSearch(event) {
this.renderNotesList(event.target.value); if (!event || !event.target) return;
this.renderNotesList(event.target.value || '');
} }
} }