mirror of
https://github.com/R0m1k3/noteflow.git
synced 2026-10-11 17:29:37 +02:00
[dyad] wrote 3 file(s)
This commit is contained in:
1 parent
3c0bd5adf2
commit
b8a19a25bf
3 files changed
+449
No files matched your search
@@ -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 = `
|
||||
<div>
|
||||
<span class="font-medium">${user.username}</span>
|
||||
${user.is_admin ? '<span class="ml-2 text-sm text-blue-600">Admin</span>' : ''}
|
||||
</div>
|
||||
<div class="actions">
|
||||
${user.id !== 1 ? `
|
||||
<button class="text-gray-600 hover:text-blue-600" onclick="window.admin.changePassword(${user.id})">
|
||||
<i data-lucide="key"></i>
|
||||
</button>
|
||||
<button class="text-gray-600 hover:text-blue-600" onclick="window.admin.toggleAdmin(${user.id}, ${user.is_admin})">
|
||||
<i data-lucide="shield"></i>
|
||||
</button>
|
||||
<button class="text-gray-600 hover:text-red-600" onclick="window.admin.deleteUser(${user.id})">
|
||||
<i data-lucide="trash-2"></i>
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
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();
|
||||
@@ -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();
|
||||
@@ -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 = `
|
||||
<h3 class="font-medium">${note.title || 'Sans titre'}</h3>
|
||||
<p class="text-sm text-gray-500 truncate">${note.content || ''}</p>
|
||||
`;
|
||||
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 = `
|
||||
<input type="checkbox" ${todo.completed ? 'checked' : ''}>
|
||||
<span>${todo.text}</span>
|
||||
<button class="ml-auto text-gray-400 hover:text-red-500">
|
||||
<i data-lucide="x"></i>
|
||||
</button>
|
||||
`;
|
||||
|
||||
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 = `
|
||||
<img src="/uploads/${image.filename}" alt="Note image">
|
||||
<button class="delete-btn">
|
||||
<i data-lucide="x"></i>
|
||||
</button>
|
||||
`;
|
||||
|
||||
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();
|
||||
Reference in new issue
Block a user