| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| |
|
| | const StorageManager = { |
| | |
| | save(key, data) { |
| | try { |
| | localStorage.setItem(`taskflow_${key}`, JSON.stringify(data)); |
| | return true; |
| | } catch (error) { |
| | console.error('خطأ في حفظ البيانات:', error); |
| | return false; |
| | } |
| | }, |
| |
|
| | |
| | load(key, defaultValue = null) { |
| | try { |
| | const data = localStorage.getItem(`taskflow_${key}`); |
| | return data ? JSON.parse(data) : defaultValue; |
| | } catch (error) { |
| | console.error('خطأ في قراءة البيانات:', error); |
| | return defaultValue; |
| | } |
| | }, |
| |
|
| | |
| | remove(key) { |
| | try { |
| | localStorage.removeItem(`taskflow_${key}`); |
| | return true; |
| | } catch (error) { |
| | console.error('خطأ في حذف البيانات:', error); |
| | return false; |
| | } |
| | }, |
| |
|
| | |
| | clearAll() { |
| | const keys = ['tasks', 'projects', 'team', 'settings', 'user']; |
| | keys.forEach(key => this.remove(key)); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| |
|
| | const NotificationManager = { |
| | notifications: [], |
| | |
| | init() { |
| | this.notifications = StorageManager.load('notifications', this.getDefaultNotifications()); |
| | this.render(); |
| | }, |
| | |
| | getDefaultNotifications() { |
| | return [ |
| | { |
| | id: 1, |
| | type: 'task', |
| | title: 'مهمة جديدة مُضافة', |
| | message: 'تم إضافة مهمة "تحديث التقرير الأسبوعي" بنجاح', |
| | time: 'منذ 5 دقائق', |
| | read: false |
| | }, |
| | { |
| | id: 2, |
| | type: 'comment', |
| | title: 'تعليق جديد', |
| | message: 'أحمد علّاش على مهمة "تصميم الواجهة"', |
| | time: 'منذ ساعة', |
| | read: false |
| | }, |
| | { |
| | id: 3, |
| | type: 'project', |
| | title: 'تحديث المشروع', |
| | message: 'تم تحديث حالة مشروع "تطبيق الجوال"', |
| | time: 'منذ ساعتين', |
| | read: false |
| | } |
| | ]; |
| | }, |
| | |
| | add(notification) { |
| | const newNotification = { |
| | id: Date.now(), |
| | time: 'الآن', |
| | read: false, |
| | ...notification |
| | }; |
| | this.notifications.unshift(newNotification); |
| | StorageManager.save('notifications', this.notifications); |
| | this.render(); |
| | this.updateCount(); |
| | }, |
| | |
| | markAsRead(id) { |
| | const notification = this.notifications.find(n => n.id === id); |
| | if (notification) { |
| | notification.read = true; |
| | StorageManager.save('notifications', this.notifications); |
| | this.render(); |
| | this.updateCount(); |
| | } |
| | }, |
| | |
| | markAllAsRead() { |
| | this.notifications.forEach(n => n.read = true); |
| | StorageManager.save('notifications', this.notifications); |
| | this.render(); |
| | this.updateCount(); |
| | }, |
| | |
| | updateCount() { |
| | const unreadCount = this.notifications.filter(n => !n.read).length; |
| | const badge = document.getElementById('notification-count'); |
| | if (badge) { |
| | badge.textContent = unreadCount; |
| | badge.style.display = unreadCount > 0 ? 'flex' : 'none'; |
| | } |
| | }, |
| | |
| | render() { |
| | const container = document.getElementById('notifications-list'); |
| | if (!container) return; |
| | |
| | container.innerHTML = this.notifications.map(notification => ` |
| | <div class="notification-item ${notification.read ? '' : 'unread'}" data-id="${notification.id}"> |
| | <div class="notification-icon ${notification.type}"> |
| | <i class="fas fa-${this.getIcon(notification.type)}"></i> |
| | </div> |
| | <div class="notification-content"> |
| | <h5>${notification.title}</h5> |
| | <p>${notification.message}</p> |
| | <span class="notification-time">${notification.time}</span> |
| | </div> |
| | </div> |
| | `).join(''); |
| | |
| | |
| | container.querySelectorAll('.notification-item').forEach(item => { |
| | item.addEventListener('click', () => { |
| | const id = parseInt(item.dataset.id); |
| | this.markAsRead(id); |
| | }); |
| | }); |
| | }, |
| | |
| | getIcon(type) { |
| | const icons = { |
| | task: 'tasks', |
| | comment: 'comment', |
| | project: 'project-diagram', |
| | user: 'user' |
| | }; |
| | return icons[type] || 'bell'; |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| |
|
| | const ProjectManager = { |
| | projects: [], |
| | |
| | init() { |
| | this.projects = StorageManager.load('projects', this.getSampleProjects()); |
| | this.renderProjectsList(); |
| | this.renderProjectsGrid(); |
| | this.renderKanbanFilter(); |
| | }, |
| | |
| | getSampleProjects() { |
| | return [ |
| | { |
| | id: 1, |
| | name: 'تطوير تطبيق الجوال', |
| | description: 'تطوير تطبيق جوال متكامل لادارة المهام والمشاريع', |
| | color: '#4a90d9', |
| | deadline: '2025-03-15', |
| | tasks: [ |
| | { id: 1, title: 'تصميم واجهة المستخدم', status: 'completed' }, |
| | { id: 2, title: 'برمجة الشاشة الرئيسية', status: 'in-progress' }, |
| | { id: 3, title: 'اختبار الأداء', status: 'pending' } |
| | ] |
| | }, |
| | { |
| | id: 2, |
| | name: 'مشروع الموقع الإلكتروني', |
| | description: 'إعادة تصميم وتطوير الموقع الإلكتروني للشركة', |
| | color: '#28a745', |
| | deadline: '2025-02-28', |
| | tasks: [ |
| | { id: 4, title: 'تحليل المتطلبات', status: 'completed' }, |
| | { id: 5, title: 'تصميم الهوية البصرية', status: 'completed' } |
| | ] |
| | }, |
| | { |
| | id: 3, |
| | name: 'حملة التسويق الرقمي', |
| | description: 'تنفيذ حملة تسويقية شاملة للمنتجات الجديدة', |
| | color: '#fd7e14', |
| | deadline: '2025-04-01', |
| | tasks: [ |
| | { id: 6, title: 'وضع الاستراتيجية', status: 'in-progress' } |
| | ] |
| | } |
| | ]; |
| | }, |
| | |
| | add(project) { |
| | const newProject = { |
| | id: Date.now(), |
| | tasks: [], |
| | ...project |
| | }; |
| | this.projects.push(newProject); |
| | StorageManager.save('projects', this.projects); |
| | this.renderProjectsList(); |
| | this.renderProjectsGrid(); |
| | this.renderKanbanFilter(); |
| | return newProject; |
| | }, |
| | |
| | update(id, updates) { |
| | const index = this.projects.findIndex(p => p.id === id); |
| | if (index !== -1) { |
| | this.projects[index] = { ...this.projects[index], ...updates }; |
| | StorageManager.save('projects', this.projects); |
| | this.renderProjectsList(); |
| | this.renderProjectsGrid(); |
| | this.renderKanbanFilter(); |
| | return true; |
| | } |
| | return false; |
| | }, |
| | |
| | delete(id) { |
| | this.projects = this.projects.filter(p => p.id !== id); |
| | StorageManager.save('projects', this.projects); |
| | this.renderProjectsList(); |
| | this.renderProjectsGrid(); |
| | this.renderKanbanFilter(); |
| | }, |
| | |
| | getProject(id) { |
| | return this.projects.find(p => p.id === id); |
| | }, |
| | |
| | getProjectName(id) { |
| | const project = this.getProject(id); |
| | return project ? project.name : 'بدون مشروع'; |
| | }, |
| | |
| | renderProjectsList() { |
| | const select = document.getElementById('task-filter-project'); |
| | const taskProjectSelect = document.getElementById('task-project-select'); |
| | const kanbanFilter = document.getElementById('kanban-project-filter'); |
| | |
| | if (select) { |
| | select.innerHTML = '<option value="all">جميع المشاريع</option>' + |
| | this.projects.map(p => `<option value="${p.id}">${p.name}</option>`).join(''); |
| | } |
| | |
| | if (taskProjectSelect) { |
| | taskProjectSelect.innerHTML = '<option value="">اختر المشروع</option>' + |
| | this.projects.map(p => `<option value="${p.id}">${p.name}</option>`).join(''); |
| | } |
| | |
| | if (kanbanFilter) { |
| | kanbanFilter.innerHTML = '<option value="all">جميع المشاريع</option>' + |
| | this.projects.map(p => `<option value="${p.id}">${p.name}</option>`).join(''); |
| | } |
| | }, |
| | |
| | renderProjectsGrid() { |
| | const grid = document.getElementById('projects-grid'); |
| | const empty = document.getElementById('empty-projects'); |
| | |
| | if (!grid) return; |
| | |
| | if (this.projects.length === 0) { |
| | grid.innerHTML = ''; |
| | if (empty) empty.classList.add('show'); |
| | return; |
| | } |
| | |
| | if (empty) empty.classList.remove('show'); |
| | |
| | grid.innerHTML = this.projects.map(project => { |
| | const completedTasks = project.tasks.filter(t => t.status === 'completed').length; |
| | const totalTasks = project.tasks.length; |
| | const progress = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0; |
| | |
| | return ` |
| | <div class="project-card" data-id="${project.id}"> |
| | <div class="project-header"> |
| | <div class="project-color-bar" style="background: linear-gradient(135deg, ${project.color}, ${project.color}dd)"></div> |
| | <div class="project-actions-overlay"> |
| | <button onclick="ProjectManager.editProject(${project.id})" title="تعديل"> |
| | <i class="fas fa-edit"></i> |
| | </button> |
| | <button onclick="ProjectManager.deleteProject(${project.id})" title="حذف"> |
| | <i class="fas fa-trash"></i> |
| | </button> |
| | </div> |
| | </div> |
| | <div class="project-body"> |
| | <h3 class="project-title">${project.name}</h3> |
| | <p class="project-description">${project.description || 'لا يوجد وصف'}</p> |
| | <div class="project-meta"> |
| | <div class="project-progress"> |
| | <div class="project-progress-bar"> |
| | <div class="project-progress-fill" style="width: ${progress}%"></div> |
| | </div> |
| | <span class="project-progress-text">${progress}%</span> |
| | </div> |
| | <span class="project-tasks-count">${totalTasks} مهام</span> |
| | </div> |
| | </div> |
| | </div> |
| | `; |
| | }).join(''); |
| | }, |
| | |
| | renderKanbanFilter() { |
| | |
| | }, |
| | |
| | editProject(id) { |
| | const project = this.getProject(id); |
| | if (!project) return; |
| | |
| | document.getElementById('project-id').value = id; |
| | document.getElementById('project-name').value = project.name; |
| | document.getElementById('project-description').value = project.description || ''; |
| | document.getElementById('project-color').value = project.color; |
| | document.getElementById('project-deadline').value = project.deadline || ''; |
| | document.getElementById('project-modal-title').textContent = 'تعديل المشروع'; |
| | |
| | ModalManager.open('project-modal'); |
| | }, |
| | |
| | deleteProject(id) { |
| | if (confirm('هل أنت متأكد من حذف هذا المشروع؟')) { |
| | this.delete(id); |
| | ToastManager.show('تم حذف المشروع بنجاح', 'success'); |
| | } |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| |
|
| | const TaskManager = { |
| | tasks: [], |
| | |
| | init() { |
| | this.tasks = StorageManager.load('tasks', this.getSampleTasks()); |
| | this.renderTasksList(); |
| | this.renderKanbanBoard(); |
| | this.renderDashboard(); |
| | this.renderCalendar(); |
| | }, |
| | |
| | getSampleTasks() { |
| | return [ |
| | { |
| | id: 1, |
| | title: 'إعداد التقرير الأسبوعي', |
| | description: 'إعداد تقرير شامل عن أداء الفريق خلال الأسبوع', |
| | projectId: 1, |
| | priority: 'high', |
| | status: 'in-progress', |
| | dueDate: '2025-01-20', |
| | featured: true, |
| | createdAt: new Date().toISOString() |
| | }, |
| | { |
| | id: 2, |
| | title: 'مراجعة التصميمات', |
| | description: 'مراجعة واعتماد التصميمات الجديدة للتطبيق', |
| | projectId: 1, |
| | priority: 'medium', |
| | status: 'pending', |
| | dueDate: '2025-01-22', |
| | featured: false, |
| | createdAt: new Date().toISOString() |
| | }, |
| | { |
| | id: 3, |
| | title: 'اختبار الوظائف', |
| | description: 'اختبار جميع وظائف التطبيق والتحقق من عدم وجود أخطاء', |
| | projectId: 1, |
| | priority: 'high', |
| | status: 'pending', |
| | dueDate: '2025-01-25', |
| | featured: true, |
| | createdAt: new Date().toISOString() |
| | }, |
| | { |
| | id: 4, |
| | title: 'تحديث الوثائق', |
| | description: 'تحديث وثائق المستخدم والتطوير', |
| | projectId: 2, |
| | priority: 'low', |
| | status: 'completed', |
| | dueDate: '2025-01-18', |
| | featured: false, |
| | createdAt: new Date().toISOString() |
| | }, |
| | { |
| | id: 5, |
| | title: 'اجتماع مع الفريق', |
| | description: 'اجتماع أسبوعي لمناقشة التقدم والمشاكل', |
| | projectId: 2, |
| | priority: 'medium', |
| | status: 'pending', |
| | dueDate: '2025-01-21', |
| | featured: false, |
| | createdAt: new Date().toISOString() |
| | }, |
| | { |
| | id: 6, |
| | title: 'إعداد الميزانية', |
| | description: 'إعداد ميزانية المشروع للربع القادم', |
| | projectId: 3, |
| | priority: 'high', |
| | status: 'in-progress', |
| | dueDate: '2025-01-23', |
| | featured: true, |
| | createdAt: new Date().toISOString() |
| | } |
| | ]; |
| | }, |
| | |
| | add(task) { |
| | const newTask = { |
| | id: Date.now(), |
| | status: 'pending', |
| | createdAt: new Date().toISOString(), |
| | ...task |
| | }; |
| | this.tasks.push(newTask); |
| | StorageManager.save('tasks', this.tasks); |
| | this.renderAll(); |
| | return newTask; |
| | }, |
| | |
| | update(id, updates) { |
| | const index = this.tasks.findIndex(t => t.id === id); |
| | if (index !== -1) { |
| | this.tasks[index] = { ...this.tasks[index], ...updates, updatedAt: new Date().toISOString() }; |
| | StorageManager.save('tasks', this.tasks); |
| | this.renderAll(); |
| | return true; |
| | } |
| | return false; |
| | }, |
| | |
| | delete(id) { |
| | this.tasks = this.tasks.filter(t => t.id !== id); |
| | StorageManager.save('tasks', this.tasks); |
| | this.renderAll(); |
| | }, |
| | |
| | toggleComplete(id) { |
| | const task = this.tasks.find(t => t.id === id); |
| | if (task) { |
| | task.status = task.status === 'completed' ? 'pending' : 'completed'; |
| | StorageManager.save('tasks', this.tasks); |
| | this.renderAll(); |
| | } |
| | }, |
| | |
| | getTask(id) { |
| | return this.tasks.find(t => t.id === id); |
| | }, |
| | |
| | getFilteredTasks(filters = {}) { |
| | let filtered = [...this.tasks]; |
| | |
| | if (filters.project && filters.project !== 'all') { |
| | filtered = filtered.filter(t => t.projectId == filters.project); |
| | } |
| | |
| | if (filters.status && filters.status !== 'all') { |
| | filtered = filtered.filter(t => t.status === filters.status); |
| | } |
| | |
| | if (filters.priority && filters.priority !== 'all') { |
| | filtered = filtered.filter(t => t.priority === filters.priority); |
| | } |
| | |
| | return filtered; |
| | }, |
| | |
| | renderAll() { |
| | this.renderTasksList(); |
| | this.renderKanbanBoard(); |
| | this.renderDashboard(); |
| | this.renderCalendar(); |
| | }, |
| | |
| | renderTasksList() { |
| | const container = document.getElementById('tasks-list'); |
| | const empty = document.getElementById('empty-tasks'); |
| | |
| | if (!container) return; |
| | |
| | const projectFilter = document.getElementById('task-filter-project')?.value || 'all'; |
| | const statusFilter = document.getElementById('task-filter-status')?.value || 'all'; |
| | const priorityFilter = document.getElementById('task-filter-priority')?.value || 'all'; |
| | |
| | const tasks = this.getFilteredTasks({ |
| | project: projectFilter, |
| | status: statusFilter, |
| | priority: priorityFilter |
| | }); |
| | |
| | if (tasks.length === 0) { |
| | container.innerHTML = ''; |
| | if (empty) empty.classList.add('show'); |
| | return; |
| | } |
| | |
| | if (empty) empty.classList.remove('show'); |
| | |
| | container.innerHTML = tasks.map(task => ` |
| | <div class="task-row ${task.status}" data-id="${task.id}"> |
| | <div class="task-checkbox" onclick="TaskManager.toggleComplete(${task.id})"> |
| | ${task.status === 'completed' ? '<i class="fas fa-check"></i>' : ''} |
| | </div> |
| | <div class="task-row-title">${task.title}</div> |
| | <div class="task-project">${ProjectManager.getProjectName(task.projectId)}</div> |
| | <div class="task-priority"> |
| | <span class="task-priority-badge ${task.priority}"> |
| | ${this.getPriorityLabel(task.priority)} |
| | </span> |
| | </div> |
| | <div class="task-due-date">${this.formatDate(task.dueDate)}</div> |
| | <div class="task-status"> |
| | <span class="task-priority-badge ${task.status}"> |
| | ${this.getStatusLabel(task.status)} |
| | </span> |
| | </div> |
| | <div class="task-row-actions"> |
| | <button onclick="TaskManager.viewTask(${task.id})" title="عرض"> |
| | <i class="fas fa-eye"></i> |
| | </button> |
| | <button onclick="TaskManager.editTask(${task.id})" title="تعديل"> |
| | <i class="fas fa-edit"></i> |
| | </button> |
| | <button class="delete" onclick="TaskManager.deleteTask(${task.id})" title="حذف"> |
| | <i class="fas fa-trash"></i> |
| | </button> |
| | </div> |
| | </div> |
| | `).join(''); |
| | }, |
| | |
| | renderKanbanBoard() { |
| | const projectFilter = document.getElementById('kanban-project-filter')?.value || 'all'; |
| | |
| | const tasks = this.getFilteredTasks({ project: projectFilter }); |
| | |
| | const columns = { |
| | pending: document.getElementById('kanban-pending'), |
| | 'in-progress': document.getElementById('kanban-in-progress'), |
| | completed: document.getElementById('kanban-completed') |
| | }; |
| | |
| | Object.keys(columns).forEach(status => { |
| | if (!columns[status]) return; |
| | |
| | const statusTasks = tasks.filter(t => t.status === status); |
| | |
| | columns[status].innerHTML = statusTasks.map(task => ` |
| | <div class="kanban-card" draggable="true" data-id="${task.id}"> |
| | <div class="kanban-card-title">${task.title}</div> |
| | <div class="kanban-card-meta"> |
| | <span class="kanban-card-project">${ProjectManager.getProjectName(task.projectId)}</span> |
| | <span class="task-priority-badge ${task.priority}"> |
| | ${this.getPriorityLabel(task.priority)} |
| | </span> |
| | </div> |
| | </div> |
| | `).join(''); |
| | |
| | |
| | const column = columns[status].closest('.kanban-column'); |
| | if (column) { |
| | column.querySelector('.task-count').textContent = statusTasks.length; |
| | } |
| | }); |
| | |
| | this.initKanbanDragDrop(); |
| | }, |
| | |
| | initKanbanDragDrop() { |
| | const cards = document.querySelectorAll('.kanban-card'); |
| | const columns = document.querySelectorAll('.column-content'); |
| | |
| | cards.forEach(card => { |
| | card.addEventListener('dragstart', (e) => { |
| | card.classList.add('dragging'); |
| | e.dataTransfer.setData('text/plain', card.dataset.id); |
| | }); |
| | |
| | card.addEventListener('dragend', () => { |
| | card.classList.remove('dragging'); |
| | }); |
| | }); |
| | |
| | columns.forEach(column => { |
| | column.addEventListener('dragover', (e) => { |
| | e.preventDefault(); |
| | const afterElement = this.getDragAfterElement(column, e.clientY); |
| | const draggable = document.querySelector('.dragging'); |
| | if (afterElement == null) { |
| | column.appendChild(draggable); |
| | } else { |
| | column.insertBefore(draggable, afterElement); |
| | } |
| | }); |
| | |
| | column.addEventListener('drop', (e) => { |
| | e.preventDefault(); |
| | const taskId = parseInt(e.dataTransfer.getData('text/plain')); |
| | const newStatus = column.id.replace('kanban-', ''); |
| | this.update(taskId, { status: newStatus }); |
| | }); |
| | }); |
| | }, |
| | |
| | getDragAfterElement(container, y) { |
| | const draggableElements = [...container.querySelectorAll('.kanban-card:not(.dragging)')]; |
| | |
| | return draggableElements.reduce((closest, child) => { |
| | const box = child.getBoundingClientRect(); |
| | const offset = y - box.top - box.height / 2; |
| | |
| | if (offset < 0 && offset > closest.offset) { |
| | return { offset: offset, element: child }; |
| | } else { |
| | return closest; |
| | } |
| | }, { offset: Number.NEGATIVE_INFINITY }).element; |
| | }, |
| | |
| | renderDashboard() { |
| | |
| | const totalTasks = this.tasks.length; |
| | const completedTasks = this.tasks.filter(t => t.status === 'completed').length; |
| | const pendingTasks = this.tasks.filter(t => t.status !== 'completed').length; |
| | const totalProjects = ProjectManager.projects.length; |
| | |
| | const totalEl = document.getElementById('total-tasks'); |
| | const completedEl = document.getElementById('completed-tasks'); |
| | const pendingEl = document.getElementById('pending-tasks'); |
| | const projectsEl = document.getElementById('total-projects'); |
| | |
| | if (totalEl) totalEl.textContent = totalTasks; |
| | if (completedEl) completedEl.textContent = completedTasks; |
| | if (pendingEl) pendingEl.textContent = pendingTasks; |
| | if (projectsEl) projectsEl.textContent = totalProjects; |
| | |
| | |
| | const completionPercentage = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0; |
| | const progressFill = document.getElementById('progress-fill'); |
| | const completionPercent = document.getElementById('completion-percentage'); |
| | |
| | if (progressFill) progressFill.style.width = `${completionPercentage}%`; |
| | if (completionPercent) completionPercent.textContent = `${completionPercentage}%`; |
| | |
| | |
| | const featuredTasks = this.tasks.filter(t => t.featured).slice(0, 5); |
| | const featuredContainer = document.getElementById('featured-tasks'); |
| | |
| | if (featuredContainer) { |
| | featuredContainer.innerHTML = featuredTasks.map(task => ` |
| | <li class="task-item ${task.status}" onclick="TaskManager.viewTask(${task.id})"> |
| | <div class="task-checkbox" onclick="event.stopPropagation(); TaskManager.toggleComplete(${task.id})"> |
| | ${task.status === 'completed' ? '<i class="fas fa-check"></i>' : ''} |
| | </div> |
| | <span class="task-text">${task.title}</span> |
| | <span class="task-priority-badge ${task.priority}"> |
| | ${this.getPriorityLabel(task.priority)} |
| | </span> |
| | </li> |
| | `).join(''); |
| | } |
| | |
| | |
| | const today = new Date().toISOString().split('T')[0]; |
| | const todayTasks = this.tasks.filter(t => t.dueDate === today).slice(0, 5); |
| | const todayContainer = document.getElementById('today-tasks'); |
| | |
| | if (todayContainer) { |
| | todayContainer.innerHTML = todayTasks.length > 0 ? todayTasks.map(task => ` |
| | <li class="task-item ${task.status}" onclick="TaskManager.viewTask(${task.id})"> |
| | <div class="task-checkbox" onclick="event.stopPropagation(); TaskManager.toggleComplete(${task.id})"> |
| | ${task.status === 'completed' ? '<i class="fas fa-check"></i>' : ''} |
| | </div> |
| | <span class="task-text">${task.title}</span> |
| | </li> |
| | `).join('') : '<li style="text-align: center; color: var(--text-muted); padding: 20px;">لا توجد مهام اليوم</li>'; |
| | } |
| | |
| | |
| | const highCount = this.tasks.filter(t => t.priority === 'high').length; |
| | const mediumCount = this.tasks.filter(t => t.priority === 'medium').length; |
| | const lowCount = this.tasks.filter(t => t.priority === 'low').length; |
| | const total = highCount + mediumCount + lowCount || 1; |
| | |
| | const highBar = document.getElementById('high-priority-bar'); |
| | const mediumBar = document.getElementById('medium-priority-bar'); |
| | const lowBar = document.getElementById('low-priority-bar'); |
| | |
| | if (highBar) highBar.style.width = `${(highCount / total) * 100}%`; |
| | if (mediumBar) mediumBar.style.width = `${(mediumCount / total) * 100}%`; |
| | if (lowBar) lowBar.style.width = `${(lowCount / total) * 100}%`; |
| | |
| | document.getElementById('high-priority-count').textContent = highCount; |
| | document.getElementById('medium-priority-count').textContent = mediumCount; |
| | document.getElementById('low-priority-count').textContent = lowCount; |
| | }, |
| | |
| | renderCalendar() { |
| | const grid = document.getElementById('calendar-grid'); |
| | if (!grid) return; |
| | |
| | const now = new Date(); |
| | const currentMonth = parseInt(window.currentMonth || now.getMonth()); |
| | const currentYear = parseInt(window.currentYear || now.getFullYear()); |
| | |
| | const monthNames = ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', |
| | 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر']; |
| | |
| | document.getElementById('current-month').textContent = `${monthNames[currentMonth]} ${currentYear}`; |
| | |
| | const firstDay = new Date(currentYear, currentMonth, 1).getDay(); |
| | const daysInMonth = new Date(currentYear, currentMonth + 1, 0).getDate(); |
| | const daysInPrevMonth = new Date(currentYear, currentMonth, 0).getDate(); |
| | |
| | let html = ''; |
| | |
| | |
| | for (let i = firstDay - 1; i >= 0; i--) { |
| | html += `<div class="calendar-day other-month"><div class="calendar-day-number">${daysInPrevMonth - i}</div></div>`; |
| | } |
| | |
| | |
| | for (let day = 1; day <= daysInMonth; day++) { |
| | const dateStr = `${currentYear}-${String(currentMonth + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`; |
| | const dayTasks = this.tasks.filter(t => t.dueDate === dateStr); |
| | const isToday = day === now.getDate() && currentMonth === now.getMonth() && currentYear === now.getFullYear(); |
| | |
| | html += ` |
| | <div class="calendar-day ${isToday ? 'today' : ''}" data-date="${dateStr}"> |
| | <div class="calendar-day-number">${day}</div> |
| | <div class="calendar-tasks"> |
| | ${dayTasks.slice(0, 2).map(task => ` |
| | <div class="calendar-task ${task.status} ${task.priority}" title="${task.title}"> |
| | ${task.title} |
| | </div> |
| | `).join('')} |
| | ${dayTasks.length > 2 ? `<div class="calendar-task" style="background: var(--bg-tertiary);">+${dayTasks.length - 2} أخرى</div>` : ''} |
| | </div> |
| | </div> |
| | `; |
| | } |
| | |
| | |
| | const totalCells = firstDay + daysInMonth; |
| | const remainingCells = 42 - totalCells; |
| | for (let i = 1; i <= remainingCells; i++) { |
| | html += `<div class="calendar-day other-month"><div class="calendar-day-number">${i}</div></div>`; |
| | } |
| | |
| | grid.innerHTML = html; |
| | }, |
| | |
| | formatDate(dateStr) { |
| | if (!dateStr) return '-'; |
| | const date = new Date(dateStr); |
| | return date.toLocaleDateString('ar-SA', { day: 'numeric', month: 'short' }); |
| | }, |
| | |
| | getPriorityLabel(priority) { |
| | const labels = { high: 'عالية', medium: 'متوسطة', low: 'منخفضة' }; |
| | return labels[priority] || priority; |
| | }, |
| | |
| | getStatusLabel(status) { |
| | const labels = { pending: 'قيد الانتظار', 'in-progress': 'قيد العمل', completed: 'مكتمل' }; |
| | return labels[status] || status; |
| | }, |
| | |
| | editTask(id) { |
| | const task = this.getTask(id); |
| | if (!task) return; |
| | |
| | document.getElementById('task-id').value = id; |
| | document.getElementById('task-title').value = task.title; |
| | document.getElementById('task-description').value = task.description || ''; |
| | document.getElementById('task-project-select').value = task.projectId || ''; |
| | document.getElementById('task-priority').value = task.priority; |
| | document.getElementById('task-due-date').value = task.dueDate || ''; |
| | document.getElementById('task-status').value = task.status; |
| | document.getElementById('task-featured').checked = task.featured || false; |
| | document.getElementById('task-modal-title').textContent = 'تعديل المهمة'; |
| | |
| | ModalManager.open('task-modal'); |
| | }, |
| | |
| | viewTask(id) { |
| | const task = this.getTask(id); |
| | if (!task) return; |
| | |
| | const content = document.getElementById('task-details-content'); |
| | content.innerHTML = ` |
| | <div class="task-detail-header"> |
| | <h2 class="task-detail-title">${task.title}</h2> |
| | <span class="task-priority-badge ${task.priority}">${this.getPriorityLabel(task.priority)}</span> |
| | </div> |
| | <div class="task-detail-meta"> |
| | <span><i class="fas fa-project-diagram"></i> ${ProjectManager.getProjectName(task.projectId)}</span> |
| | <span><i class="fas fa-flag"></i> ${this.getPriorityLabel(task.priority)}</span> |
| | <span><i class="fas fa-calendar"></i> ${this.formatDate(task.dueDate)}</span> |
| | <span class="task-priority-badge ${task.status}">${this.getStatusLabel(task.status)}</span> |
| | </div> |
| | <div class="task-detail-description"> |
| | <h4>الوصف</h4> |
| | <p>${task.description || 'لا يوجد وصف لهذه المهمة'}</p> |
| | </div> |
| | <div class="modal-footer"> |
| | <button class="btn btn-secondary" onclick="ModalManager.close('task-details-modal')">إغلاق</button> |
| | <button class="btn btn-primary" onclick="TaskManager.editTask(${task.id}); ModalManager.close('task-details-modal');"> |
| | <i class="fas fa-edit"></i> |
| | <span>تعديل</span> |
| | </button> |
| | </div> |
| | `; |
| | |
| | ModalManager.open('task-details-modal'); |
| | }, |
| | |
| | deleteTask(id) { |
| | if (confirm('هل أنت متأكد من حذف هذه المهمة؟')) { |
| | this.delete(id); |
| | ToastManager.show('تم حذف المهمة بنجاح', 'success'); |
| | } |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| |
|
| | const TeamManager = { |
| | members: [], |
| | |
| | init() { |
| | this.members = StorageManager.load('team', this.getSampleMembers()); |
| | this.renderTeamGrid(); |
| | }, |
| | |
| | getSampleMembers() { |
| | return [ |
| | { |
| | id: 1, |
| | name: 'أحمد محمد', |
| | role: 'مطور أمامي', |
| | avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Ahmed', |
| | tasksCompleted: 45, |
| | tasksInProgress: 5 |
| | }, |
| | { |
| | id: 2, |
| | name: 'سارة علي', |
| | role: 'مصممة', |
| | avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Sarah', |
| | tasksCompleted: 38, |
| | tasksInProgress: 8 |
| | }, |
| | { |
| | id: 3, |
| | name: 'خالد عمر', |
| | role: 'مطور خلفي', |
| | avatar: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Khalid', |
| | tasksCompleted: 52, |
| | tasksInProgress: 3 |
| | } |
| | ]; |
| | }, |
| | |
| | add(member) { |
| | const newMember = { |
| | id: Date.now(), |
| | tasksCompleted: 0, |
| | tasksInProgress: 1, |
| | ...member |
| | }; |
| | this.members.push(newMember); |
| | StorageManager.save('team', this.members); |
| | this.renderTeamGrid(); |
| | return newMember; |
| | }, |
| | |
| | renderTeamGrid() { |
| | const grid = document.getElementById('team-grid'); |
| | const empty = document.getElementById('empty-team'); |
| | |
| | if (!grid) return; |
| | |
| | if (this.members.length === 0) { |
| | grid.innerHTML = ''; |
| | if (empty) empty.classList.add('show'); |
| | return; |
| | } |
| | |
| | if (empty) empty.classList.remove('show'); |
| | |
| | grid.innerHTML = this.members.map((member, index) => ` |
| | <div class="team-member-card"> |
| | <img src="${member.avatar}" alt="${member.name}" class="member-avatar"> |
| | <h3 class="member-name">${member.name}</h3> |
| | <p class="member-role">${member.role}</p> |
| | <div class="member-stats"> |
| | <div class="member-stat"> |
| | <div class="member-stat-value">${member.tasksCompleted}</div> |
| | <div class="member-stat-label">مهام مكتملة</div> |
| | </div> |
| | <div class="member-stat"> |
| | <div class="member-stat-value">${member.tasksInProgress}</div> |
| | <div class="member-stat-label">قيد العمل</div> |
| | </div> |
| | </div> |
| | </div> |
| | `).join(''); |
| | |
| | |
| | this.renderTopPerformers(); |
| | }, |
| | |
| | renderTopPerformers() { |
| | const container = document.getElementById('top-performers'); |
| | if (!container) return; |
| | |
| | const sorted = [...this.members].sort((a, b) => b.tasksCompleted - a.tasksCompleted).slice(0, 5); |
| | |
| | container.innerHTML = sorted.map((member, index) => ` |
| | <div class="performer-item"> |
| | <div class="performer-rank ${index === 0 ? 'gold' : index === 1 ? 'silver' : index === 2 ? 'bronze' : ''}"> |
| | ${index + 1} |
| | </div> |
| | <div class="performer-info"> |
| | <div class="performer-name">${member.name}</div> |
| | <div class="performer-tasks">${member.tasksCompleted} مهمة مكتملة</div> |
| | </div> |
| | </div> |
| | `).join(''); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| |
|
| | const ModalManager = { |
| | open(modalId) { |
| | const modal = document.getElementById(modalId); |
| | if (modal) { |
| | modal.classList.add('active'); |
| | document.body.style.overflow = 'hidden'; |
| | } |
| | }, |
| | |
| | close(modalId) { |
| | const modal = document.getElementById(modalId); |
| | if (modal) { |
| | modal.classList.remove('active'); |
| | document.body.style.overflow = ''; |
| | } |
| | }, |
| | |
| | init() { |
| | |
| | document.querySelectorAll('.modal-overlay').forEach(overlay => { |
| | overlay.addEventListener('click', () => { |
| | const modal = overlay.closest('.modal'); |
| | if (modal) { |
| | this.close(modal.id); |
| | } |
| | }); |
| | }); |
| | |
| | |
| | document.querySelectorAll('.modal-close, .modal-cancel').forEach(btn => { |
| | btn.addEventListener('click', () => { |
| | const modalId = btn.dataset.modal; |
| | if (modalId) { |
| | this.close(modalId); |
| | } |
| | }); |
| | }); |
| | |
| | |
| | document.addEventListener('keydown', (e) => { |
| | if (e.key === 'Escape') { |
| | document.querySelectorAll('.modal.active').forEach(modal => { |
| | this.close(modal.id); |
| | }); |
| | } |
| | }); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| |
|
| | const ToastManager = { |
| | show(message, type = 'info', duration = 3000) { |
| | const container = document.getElementById('toast-container'); |
| | if (!container) return; |
| | |
| | const toast = document.createElement('div'); |
| | toast.className = `toast ${type}`; |
| | |
| | const icons = { |
| | success: 'check-circle', |
| | error: 'times-circle', |
| | warning: 'exclamation-triangle', |
| | info: 'info-circle' |
| | }; |
| | |
| | toast.innerHTML = ` |
| | <i class="fas fa-${icons[type]} toast-icon"></i> |
| | <span class="toast-message">${message}</span> |
| | `; |
| | |
| | container.appendChild(toast); |
| | |
| | setTimeout(() => { |
| | toast.style.animation = 'toastSlideIn 0.3s ease reverse'; |
| | setTimeout(() => toast.remove(), 300); |
| | }, duration); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| |
|
| | const SettingsManager = { |
| | settings: {}, |
| | |
| | init() { |
| | this.settings = StorageManager.load('settings', { |
| | darkMode: false, |
| | primaryColor: 'blue', |
| | fontSize: 'medium', |
| | language: 'ar', |
| | notifications: { |
| | newTask: true, |
| | dueDate: true, |
| | comments: true, |
| | email: false |
| | } |
| | }); |
| | |
| | this.applySettings(); |
| | this.setupEventListeners(); |
| | }, |
| | |
| | save() { |
| | StorageManager.save('settings', this.settings); |
| | }, |
| | |
| | applySettings() { |
| | |
| | if (this.settings.darkMode) { |
| | document.body.setAttribute('data-theme', 'dark'); |
| | document.getElementById('dark-mode-toggle').checked = true; |
| | } else { |
| | document.body.removeAttribute('data-theme'); |
| | document.getElementById('dark-mode-toggle').checked = false; |
| | } |
| | |
| | |
| | document.body.setAttribute('data-color', this.settings.primaryColor); |
| | document.querySelectorAll('.color-option').forEach(btn => { |
| | btn.classList.toggle('active', btn.dataset.color === this.settings.primaryColor); |
| | }); |
| | |
| | |
| | document.body.setAttribute('data-font-size', this.settings.fontSize); |
| | document.getElementById('font-size').value = this.settings.fontSize; |
| | |
| | |
| | document.getElementById('interface-language').value = this.settings.language; |
| | document.getElementById('text-direction').value = this.settings.language === 'ar' ? 'rtl' : 'ltr'; |
| | |
| | |
| | if (document.getElementById('notify-new-task')) { |
| | document.getElementById('notify-new-task').checked = this.settings.notifications.newTask; |
| | } |
| | if (document.getElementById('notify-due-date')) { |
| | document.getElementById('notify-due-date').checked = this.settings.notifications.dueDate; |
| | } |
| | if (document.getElementById('notify-comments')) { |
| | document.getElementById('notify-comments').checked = this.settings.notifications.comments; |
| | } |
| | if (document.getElementById('notify-email')) { |
| | document.getElementById('notify-email').checked = this.settings.notifications.email; |
| | } |
| | |
| | |
| | const user = StorageManager.load('user', {}); |
| | if (user.name) { |
| | document.getElementById('settings-name').value = user.name; |
| | document.getElementById('user-name').textContent = user.name; |
| | } |
| | if (user.email) { |
| | document.getElementById('settings-email').value = user.email; |
| | } |
| | }, |
| | |
| | setupEventListeners() { |
| | |
| | document.getElementById('dark-mode-toggle')?.addEventListener('change', (e) => { |
| | this.settings.darkMode = e.target.checked; |
| | this.applySettings(); |
| | this.save(); |
| | }); |
| | |
| | |
| | document.querySelectorAll('.color-option').forEach(btn => { |
| | btn.addEventListener('click', () => { |
| | this.settings.primaryColor = btn.dataset.color; |
| | this.applySettings(); |
| | this.save(); |
| | ToastManager.show('تم تغيير اللون بنجاح', 'success'); |
| | }); |
| | }); |
| | |
| | |
| | document.getElementById('font-size')?.addEventListener('change', (e) => { |
| | this.settings.fontSize = e.target.value; |
| | this.applySettings(); |
| | this.save(); |
| | }); |
| | |
| | |
| | document.getElementById('interface-language')?.addEventListener('change', (e) => { |
| | this.settings.language = e.target.value; |
| | this.applySettings(); |
| | this.save(); |
| | ToastManager.show('يرجى إعادة تحميل الصفحة لتطبيق التغييرات', 'info'); |
| | }); |
| | |
| | |
| | document.getElementById('notify-new-task')?.addEventListener('change', (e) => { |
| | this.settings.notifications.newTask = e.target.checked; |
| | this.save(); |
| | }); |
| | |
| | document.getElementById('notify-due-date')?.addEventListener('change', (e) => { |
| | this.settings.notifications.dueDate = e.target.checked; |
| | this.save(); |
| | }); |
| | |
| | document.getElementById('notify-comments')?.addEventListener('change', (e) => { |
| | this.settings.notifications.comments = e.target.checked; |
| | this.save(); |
| | }); |
| | |
| | document.getElementById('notify-email')?.addEventListener('change', (e) => { |
| | this.settings.notifications.email = e.target.checked; |
| | this.save(); |
| | }); |
| | |
| | |
| | document.getElementById('profile-form')?.addEventListener('submit', (e) => { |
| | e.preventDefault(); |
| | const user = { |
| | name: document.getElementById('settings-name').value, |
| | email: document.getElementById('settings-email').value, |
| | phone: document.getElementById('settings-phone').value, |
| | bio: document.getElementById('settings-bio').value |
| | }; |
| | StorageManager.save('user', user); |
| | document.getElementById('user-name').textContent = user.name || 'مستخدم جديد'; |
| | ToastManager.show('تم حفظ التغييرات بنجاح', 'success'); |
| | }); |
| | |
| | |
| | document.getElementById('security-form')?.addEventListener('submit', (e) => { |
| | e.preventDefault(); |
| | const current = document.getElementById('current-password').value; |
| | const newPass = document.getElementById('new-password').value; |
| | const confirm = document.getElementById('confirm-new-password').value; |
| | |
| | if (!current) { |
| | ToastManager.show('الرجاء إدخال كلمة المرور الحالية', 'error'); |
| | return; |
| | } |
| | |
| | if (newPass !== confirm) { |
| | ToastManager.show('كلمتا المرور غير متطابقتين', 'error'); |
| | return; |
| | } |
| | |
| | if (newPass.length < 6) { |
| | ToastManager.show('كلمة المرور يجب أن تكون 6 أحرف على الأقل', 'error'); |
| | return; |
| | } |
| | |
| | ToastManager.show('تم تغيير كلمة المرور بنجاح', 'success'); |
| | e.target.reset(); |
| | }); |
| | |
| | |
| | document.querySelectorAll('.settings-sidebar li').forEach(item => { |
| | item.addEventListener('click', () => { |
| | const settingsId = item.dataset.settings; |
| | |
| | document.querySelectorAll('.settings-sidebar li').forEach(li => li.classList.remove('active')); |
| | document.querySelectorAll('.settings-panel').forEach(panel => panel.classList.remove('active')); |
| | |
| | item.classList.add('active'); |
| | document.getElementById(`settings-${settingsId}`)?.classList.add('active'); |
| | }); |
| | }); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| |
|
| | const AuthManager = { |
| | user: null, |
| | |
| | init() { |
| | this.user = StorageManager.load('currentUser', null); |
| | |
| | if (this.user) { |
| | this.showMainApp(); |
| | } else { |
| | this.showLoginPage(); |
| | } |
| | |
| | this.setupEventListeners(); |
| | }, |
| | |
| | login(email, password) { |
| | |
| | if (email && password.length >= 6) { |
| | this.user = { |
| | email: email, |
| | name: email.split('@')[0], |
| | avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${email}` |
| | }; |
| | StorageManager.save('currentUser', this.user); |
| | this.showMainApp(); |
| | ToastManager.show('مرحباً بعودتك!', 'success'); |
| | return true; |
| | } |
| | ToastManager.show('البريد الإلكتروني أو كلمة المرور غير صحيحة', 'error'); |
| | return false; |
| | }, |
| | |
| | register(name, email, password) { |
| | if (name && email && password.length >= 6) { |
| | this.user = { |
| | name: name, |
| | email: email, |
| | avatar: `https://api.dicebear.com/7.x/avataaars/svg?seed=${name}` |
| | }; |
| | StorageManager.save('currentUser', this.user); |
| | this.showMainApp(); |
| | ToastManager.show('تم إنشاء حسابك بنجاح!', 'success'); |
| | return true; |
| | } |
| | ToastManager.show('الرجاء تعبئة جميع الحقول بشكل صحيح', 'error'); |
| | return false; |
| | }, |
| | |
| | logout() { |
| | this.user = null; |
| | StorageManager.remove('currentUser'); |
| | this.showLoginPage(); |
| | ToastManager.show('تم تسجيل الخروج بنجاح', 'info'); |
| | }, |
| | |
| | showLoginPage() { |
| | document.getElementById('login-page').classList.remove('hidden'); |
| | document.getElementById('main-app').classList.add('hidden'); |
| | document.getElementById('loading-screen').classList.add('hidden'); |
| | }, |
| | |
| | showMainApp() { |
| | document.getElementById('login-page').classList.add('hidden'); |
| | document.getElementById('main-app').classList.remove('hidden'); |
| | document.getElementById('loading-screen').classList.add('hidden'); |
| | |
| | if (this.user) { |
| | document.getElementById('user-name').textContent = this.user.name || 'مستخدم جديد'; |
| | } |
| | }, |
| | |
| | setupEventListeners() { |
| | |
| | document.getElementById('login-form')?.addEventListener('submit', (e) => { |
| | e.preventDefault(); |
| | const email = document.getElementById('login-email').value; |
| | const password = document.getElementById('login-password').value; |
| | this.login(email, password); |
| | }); |
| | |
| | |
| | document.getElementById('register-form')?.addEventListener('submit', (e) => { |
| | e.preventDefault(); |
| | const name = document.getElementById('register-name').value; |
| | const email = document.getElementById('register-email').value; |
| | const password = document.getElementById('register-password').value; |
| | const confirm = document.getElementById('register-confirm').value; |
| | |
| | if (password !== confirm) { |
| | ToastManager.show('كلمتا المرور غير متطابقتين', 'error'); |
| | return; |
| | } |
| | |
| | this.register(name, email, password); |
| | }); |
| | |
| | |
| | document.getElementById('show-register')?.addEventListener('click', (e) => { |
| | e.preventDefault(); |
| | document.getElementById('login-form').classList.add('hidden'); |
| | document.getElementById('register-form').classList.remove('hidden'); |
| | }); |
| | |
| | document.getElementById('show-login')?.addEventListener('click', (e) => { |
| | e.preventDefault(); |
| | document.getElementById('register-form').classList.add('hidden'); |
| | document.getElementById('login-form').classList.remove('hidden'); |
| | }); |
| | |
| | |
| | document.getElementById('logout-btn')?.addEventListener('click', (e) => { |
| | e.preventDefault(); |
| | this.logout(); |
| | }); |
| | |
| | |
| | document.querySelectorAll('[data-action="logout"]').forEach(btn => { |
| | btn.addEventListener('click', (e) => { |
| | e.preventDefault(); |
| | this.logout(); |
| | }); |
| | }); |
| | |
| | |
| | document.querySelector('[data-action="settings"]')?.addEventListener('click', (e) => { |
| | e.preventDefault(); |
| | this.switchView('settings'); |
| | }); |
| | }, |
| | |
| | switchView(viewName) { |
| | document.querySelectorAll('.view').forEach(v => v.classList.remove('active')); |
| | document.querySelectorAll('.sidebar-nav li').forEach(l => l.classList.remove('active')); |
| | |
| | const view = document.getElementById(`${viewName}-view`); |
| | const navItem = document.querySelector(`.sidebar-nav li[data-view="${viewName}"]`); |
| | |
| | if (view) view.classList.add('active'); |
| | if (navItem) navItem.classList.add('active'); |
| | |
| | |
| | document.getElementById('user-menu')?.classList.remove('active'); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| |
|
| | const NavigationManager = { |
| | init() { |
| | |
| | document.querySelectorAll('.sidebar-nav li, .sidebar-footer li').forEach(item => { |
| | item.addEventListener('click', (e) => { |
| | e.preventDefault(); |
| | const viewName = item.dataset.view; |
| | if (viewName) { |
| | this.switchView(viewName); |
| | } |
| | }); |
| | }); |
| | |
| | |
| | document.getElementById('user-btn')?.addEventListener('click', () => { |
| | document.getElementById('user-menu')?.classList.toggle('active'); |
| | }); |
| | |
| | |
| | document.getElementById('notifications-btn')?.addEventListener('click', () => { |
| | document.getElementById('notifications-menu')?.classList.toggle('active'); |
| | }); |
| | |
| | |
| | document.querySelector('.mark-all-read')?.addEventListener('click', () => { |
| | NotificationManager.markAllAsRead(); |
| | ToastManager.show('تم تعليم جميع الإشعارات كمقروءة', 'success'); |
| | }); |
| | |
| | |
| | document.getElementById('sidebar-toggle')?.addEventListener('click', () => { |
| | document.getElementById('sidebar').classList.toggle('collapsed'); |
| | document.body.classList.toggle('sidebar-collapsed'); |
| | }); |
| | |
| | |
| | document.getElementById('menu-toggle')?.addEventListener('click', () => { |
| | document.getElementById('sidebar').classList.toggle('active'); |
| | }); |
| | |
| | |
| | document.addEventListener('click', (e) => { |
| | if (!e.target.closest('.user-dropdown')) { |
| | document.getElementById('user-menu')?.classList.remove('active'); |
| | } |
| | if (!e.target.closest('.notifications-dropdown')) { |
| | document.getElementById('notifications-menu')?.classList.remove('active'); |
| | } |
| | if (!e.target.closest('.sidebar') && !e.target.closest('.menu-toggle')) { |
| | document.getElementById('sidebar')?.classList.remove('active'); |
| | } |
| | }); |
| | |
| | |
| | document.getElementById('task-filter-project')?.addEventListener('change', () => { |
| | TaskManager.renderTasksList(); |
| | }); |
| | |
| | document.getElementById('task-filter-status')?.addEventListener('change', () => { |
| | TaskManager.renderTasksList(); |
| | }); |
| | |
| | document.getElementById('task-filter-priority')?.addEventListener('change', () => { |
| | TaskManager.renderTasksList(); |
| | }); |
| | |
| | |
| | document.getElementById('kanban-project-filter')?.addEventListener('change', () => { |
| | TaskManager.renderKanbanBoard(); |
| | }); |
| | |
| | |
| | document.querySelectorAll('.view-all').forEach(btn => { |
| | btn.addEventListener('click', (e) => { |
| | e.preventDefault(); |
| | const view = btn.dataset.view; |
| | if (view) { |
| | this.switchView(view); |
| | } |
| | }); |
| | }); |
| | }, |
| | |
| | switchView(viewName) { |
| | document.querySelectorAll('.view').forEach(v => v.classList.remove('active')); |
| | document.querySelectorAll('.sidebar-nav li').forEach(l => l.classList.remove('active')); |
| | |
| | const view = document.getElementById(`${viewName}-view`); |
| | const navItem = document.querySelector(`.sidebar-nav li[data-view="${viewName}"]`); |
| | |
| | if (view) { |
| | view.classList.add('active'); |
| | this.updateViewTitle(viewName); |
| | } |
| | |
| | if (navItem) navItem.classList.add('active'); |
| | |
| | |
| | if (viewName === 'calendar') { |
| | TaskManager.renderCalendar(); |
| | } |
| | }, |
| | |
| | updateViewTitle(viewName) { |
| | const titles = { |
| | dashboard: 'لوحة التحكم', |
| | tasks: 'المهام', |
| | projects: 'المشاريع', |
| | kanban: 'لوحة كانبان', |
| | calendar: 'التقويم', |
| | team: 'الفريق', |
| | analytics: 'التحليلات', |
| | settings: 'الإعدادات' |
| | }; |
| | |
| | const viewHeader = document.querySelector('#dashboard-view.active .view-header h1'); |
| | if (viewHeader && titles[viewName]) { |
| | viewHeader.textContent = titles[viewName]; |
| | } |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| |
|
| | const FormManager = { |
| | init() { |
| | |
| | document.getElementById('task-form')?.addEventListener('submit', (e) => { |
| | e.preventDefault(); |
| | |
| | const taskData = { |
| | title: document.getElementById('task-title').value, |
| | description: document.getElementById('task-description').value, |
| | projectId: parseInt(document.getElementById('task-project-select').value) || null, |
| | priority: document.getElementById('task-priority').value, |
| | dueDate: document.getElementById('task-due-date').value, |
| | status: document.getElementById('task-status').value, |
| | featured: document.getElementById('task-featured').checked |
| | }; |
| | |
| | const taskId = document.getElementById('task-id').value; |
| | |
| | if (taskId) { |
| | TaskManager.update(parseInt(taskId), taskData); |
| | ToastManager.show('تم تحديث المهمة بنجاح', 'success'); |
| | } else { |
| | TaskManager.add(taskData); |
| | ToastManager.show('تم إضافة المهمة بنجاح', 'success'); |
| | } |
| | |
| | ModalManager.close('task-modal'); |
| | e.target.reset(); |
| | }); |
| | |
| | |
| | document.getElementById('project-form')?.addEventListener('submit', (e) => { |
| | e.preventDefault(); |
| | |
| | const projectData = { |
| | name: document.getElementById('project-name').value, |
| | description: document.getElementById('project-description').value, |
| | color: document.getElementById('project-color').value, |
| | deadline: document.getElementById('project-deadline').value |
| | }; |
| | |
| | const projectId = document.getElementById('project-id').value; |
| | |
| | if (projectId) { |
| | ProjectManager.update(parseInt(projectId), projectData); |
| | ToastManager.show('تم تحديث المشروع بنجاح', 'success'); |
| | } else { |
| | ProjectManager.add(projectData); |
| | ToastManager.show('تم إنشاء المشروع بنجاح', 'success'); |
| | } |
| | |
| | ModalManager.close('project-modal'); |
| | e.target.reset(); |
| | }); |
| | |
| | |
| | document.getElementById('invite-form')?.addEventListener('submit', (e) => { |
| | e.preventDefault(); |
| | |
| | const inviteData = { |
| | email: document.getElementById('invite-email').value, |
| | role: document.getElementById('invite-role').value |
| | }; |
| | |
| | |
| | ToastManager.show(`تم إرسال الدعوة إلى ${inviteData.email}`, 'success'); |
| | ModalManager.close('invite-modal'); |
| | e.target.reset(); |
| | }); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| |
|
| | const ActionManager = { |
| | init() { |
| | |
| | document.getElementById('new-task-btn')?.addEventListener('click', () => this.openTaskModal()); |
| | document.getElementById('add-task-btn')?.addEventListener('click', () => this.openTaskModal()); |
| | document.getElementById('empty-add-task')?.addEventListener('click', () => this.openTaskModal()); |
| | document.getElementById('quick-add')?.addEventListener('click', () => this.toggleQuickAdd()); |
| | |
| | |
| | document.getElementById('quick-task-add')?.addEventListener('click', () => this.addQuickTask()); |
| | document.getElementById('quick-task-input')?.addEventListener('keypress', (e) => { |
| | if (e.key === 'Enter') this.addQuickTask(); |
| | }); |
| | |
| | |
| | document.getElementById('add-project-btn')?.addEventListener('click', () => this.openProjectModal()); |
| | document.getElementById('empty-add-project')?.addEventListener('click', () => this.openProjectModal()); |
| | |
| | |
| | document.getElementById('invite-member-btn')?.addEventListener('click', () => { |
| | ModalManager.open('invite-modal'); |
| | }); |
| | document.getElementById('empty-invite')?.addEventListener('click', () => { |
| | ModalManager.open('invite-modal'); |
| | }); |
| | |
| | |
| | document.querySelectorAll('.add-task-to-column').forEach(btn => { |
| | btn.addEventListener('click', () => { |
| | const status = btn.dataset.status; |
| | document.getElementById('task-status').value = status; |
| | this.openTaskModal(); |
| | }); |
| | }); |
| | |
| | |
| | document.getElementById('calendar-prev')?.addEventListener('click', () => this.navigateCalendar(-1)); |
| | document.getElementById('calendar-next')?.addEventListener('click', () => this.navigateCalendar(1)); |
| | document.getElementById('today-btn')?.addEventListener('click', () => { |
| | window.currentMonth = new Date().getMonth(); |
| | window.currentYear = new Date().getFullYear(); |
| | TaskManager.renderCalendar(); |
| | }); |
| | |
| | |
| | document.getElementById('google-login')?.addEventListener('click', () => { |
| | AuthManager.login('demo@gmail.com', '123456'); |
| | }); |
| | |
| | document.getElementById('github-login')?.addEventListener('click', () => { |
| | AuthManager.login('demo@github.com', '123456'); |
| | }); |
| | }, |
| | |
| | openTaskModal() { |
| | document.getElementById('task-id').value = ''; |
| | document.getElementById('task-form').reset(); |
| | document.getElementById('task-modal-title').textContent = 'إضافة مهمة جديدة'; |
| | ModalManager.open('task-modal'); |
| | }, |
| | |
| | openProjectModal() { |
| | document.getElementById('project-id').value = ''; |
| | document.getElementById('project-form').reset(); |
| | document.getElementById('project-modal-title').textContent = 'إنشاء مشروع جديد'; |
| | ModalManager.open('project-modal'); |
| | }, |
| | |
| | toggleQuickAdd() { |
| | const popup = document.getElementById('quick-add-popup'); |
| | popup?.classList.toggle('hidden'); |
| | if (!popup?.classList.contains('hidden')) { |
| | document.getElementById('quick-task-input')?.focus(); |
| | } |
| | }, |
| | |
| | addQuickTask() { |
| | const input = document.getElementById('quick-task-input'); |
| | const title = input.value.trim(); |
| | |
| | if (title) { |
| | TaskManager.add({ |
| | title: title, |
| | priority: 'medium', |
| | projectId: ProjectManager.projects[0]?.id || null |
| | }); |
| | ToastManager.show('تم إضافة المهمة', 'success'); |
| | input.value = ''; |
| | } |
| | }, |
| | |
| | navigateCalendar(direction) { |
| | const now = new Date(); |
| | const currentMonth = parseInt(window.currentMonth || now.getMonth()); |
| | const currentYear = parseInt(window.currentYear || now.getFullYear()); |
| | |
| | let newMonth = currentMonth + direction; |
| | let newYear = currentYear; |
| | |
| | if (newMonth > 11) { |
| | newMonth = 0; |
| | newYear++; |
| | } else if (newMonth < 0) { |
| | newMonth = 11; |
| | newYear--; |
| | } |
| | |
| | window.currentMonth = newMonth; |
| | window.currentYear = newYear; |
| | TaskManager.renderCalendar(); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| |
|
| | const ChartManager = { |
| | charts: {}, |
| | |
| | init() { |
| | this.renderTasksChart(); |
| | this.renderPriorityChart(); |
| | }, |
| | |
| | renderTasksChart() { |
| | const ctx = document.getElementById('tasks-chart'); |
| | if (!ctx) return; |
| | |
| | const tasks = TaskManager.tasks; |
| | const completed = tasks.filter(t => t.status === 'completed').length; |
| | const inProgress = tasks.filter(t => t.status === 'in-progress').length; |
| | const pending = tasks.filter(t => t.status === 'pending').length; |
| | |
| | if (this.charts.tasks) { |
| | this.charts.tasks.destroy(); |
| | } |
| | |
| | this.charts.tasks = new Chart(ctx, { |
| | type: 'bar', |
| | data: { |
| | labels: ['مكتمل', 'قيد العمل', 'قيد الانتظار'], |
| | datasets: [{ |
| | label: 'عدد المهام', |
| | data: [completed, inProgress, pending], |
| | backgroundColor: ['#28a745', '#4a90d9', '#ffc107'], |
| | borderRadius: 8 |
| | }] |
| | }, |
| | options: { |
| | responsive: true, |
| | maintainAspectRatio: false, |
| | plugins: { |
| | legend: { |
| | display: false |
| | } |
| | }, |
| | scales: { |
| | y: { |
| | beginAtZero: true, |
| | ticks: { |
| | stepSize: 1 |
| | } |
| | } |
| | } |
| | } |
| | }); |
| | }, |
| | |
| | renderPriorityChart() { |
| | const ctx = document.getElementById('priority-chart'); |
| | if (!ctx) return; |
| | |
| | const tasks = TaskManager.tasks; |
| | const high = tasks.filter(t => t.priority === 'high').length; |
| | const medium = tasks.filter(t => t.priority === 'medium').length; |
| | const low = tasks.filter(t => t.priority === 'low').length; |
| | |
| | if (this.charts.priority) { |
| | this.charts.priority.destroy(); |
| | } |
| | |
| | this.charts.priority = new Chart(ctx, { |
| | type: 'doughnut', |
| | data: { |
| | labels: ['عالية', 'متوسطة', 'منخفضة'], |
| | datasets: [{ |
| | data: [high, medium, low], |
| | backgroundColor: ['#dc3545', '#ffc107', '#28a745'], |
| | borderWidth: 0 |
| | }] |
| | }, |
| | options: { |
| | responsive: true, |
| | maintainAspectRatio: false, |
| | plugins: { |
| | legend: { |
| | position: 'bottom' |
| | } |
| | } |
| | } |
| | }); |
| | |
| | |
| | const total = tasks.length || 1; |
| | const completed = tasks.filter(t => t.status === 'completed').length; |
| | const rate = Math.round((completed / total) * 100); |
| | |
| | const completionCircle = document.getElementById('completion-circle'); |
| | const completionValue = document.getElementById('completion-rate-value'); |
| | |
| | if (completionCircle) { |
| | const circumference = 283; |
| | const offset = circumference - (rate / 100) * circumference; |
| | completionCircle.style.strokeDashoffset = offset; |
| | } |
| | |
| | if (completionValue) { |
| | completionValue.textContent = rate; |
| | } |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| |
|
| | document.addEventListener('DOMContentLoaded', () => { |
| | |
| | setTimeout(() => { |
| | document.getElementById('loading-screen').classList.add('hidden'); |
| | }, 1500); |
| | |
| | |
| | AuthManager.init(); |
| | ProjectManager.init(); |
| | TaskManager.init(); |
| | TeamManager.init(); |
| | NotificationManager.init(); |
| | SettingsManager.init(); |
| | ModalManager.init(); |
| | NavigationManager.init(); |
| | FormManager.init(); |
| | ActionManager.init(); |
| | ChartManager.init(); |
| | |
| | |
| | window.currentMonth = new Date().getMonth(); |
| | window.currentYear = new Date().getFullYear(); |
| | |
| | console.log('✅ تم تهيئة TaskFlow Pro بنجاح'); |
| | }); |
| |
|
| | |
| | window.ProjectManager = ProjectManager; |
| | window.TaskManager = TaskManager; |
| | window.ModalManager = ModalManager; |
| | window.ToastManager = ToastManager; |
| | window.AuthManager = AuthManager; |
| |
|