Spaces:
Running
Running
Tạo hệ thống quản lý công việc, người dùng đăng nhập, tạo công việc mới, điều chỉnh trạng thái, thống kê báo cáo công việc
Browse files- components/auth-form.js +115 -0
- components/navbar.js +2 -4
- components/task-filter.js +58 -0
- components/user-profile.js +50 -0
- index.html +5 -2
- script.js +53 -11
components/auth-form.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class AuthForm extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
.auth-container {
|
| 7 |
+
@apply max-w-md mx-auto p-8 bg-white rounded-lg shadow-md;
|
| 8 |
+
}
|
| 9 |
+
.auth-tabs {
|
| 10 |
+
@apply flex border-b mb-6;
|
| 11 |
+
}
|
| 12 |
+
.auth-tab {
|
| 13 |
+
@apply px-4 py-2 cursor-pointer;
|
| 14 |
+
}
|
| 15 |
+
.auth-tab.active {
|
| 16 |
+
@apply border-b-2 border-blue-500 font-medium;
|
| 17 |
+
}
|
| 18 |
+
.form-group {
|
| 19 |
+
@apply mb-4;
|
| 20 |
+
}
|
| 21 |
+
label {
|
| 22 |
+
@apply block text-sm font-medium text-gray-700 mb-1;
|
| 23 |
+
}
|
| 24 |
+
input {
|
| 25 |
+
@apply w-full px-3 py-2 border border-gray-300 rounded-md;
|
| 26 |
+
}
|
| 27 |
+
button {
|
| 28 |
+
@apply w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700;
|
| 29 |
+
}
|
| 30 |
+
.error {
|
| 31 |
+
@apply text-red-500 text-sm mt-2;
|
| 32 |
+
}
|
| 33 |
+
</style>
|
| 34 |
+
<div class="auth-container">
|
| 35 |
+
<div class="auth-tabs">
|
| 36 |
+
<div class="auth-tab active" data-tab="login">Login</div>
|
| 37 |
+
<div class="auth-tab" data-tab="register">Register</div>
|
| 38 |
+
</div>
|
| 39 |
+
<form id="loginForm">
|
| 40 |
+
<div class="form-group">
|
| 41 |
+
<label>Email</label>
|
| 42 |
+
<input type="email" id="loginEmail" required>
|
| 43 |
+
</div>
|
| 44 |
+
<div class="form-group">
|
| 45 |
+
<label>Password</label>
|
| 46 |
+
<input type="password" id="loginPassword" required>
|
| 47 |
+
</div>
|
| 48 |
+
<div id="loginError" class="error"></div>
|
| 49 |
+
<button type="submit">Login</button>
|
| 50 |
+
</form>
|
| 51 |
+
<form id="registerForm" class="hidden">
|
| 52 |
+
<div class="form-group">
|
| 53 |
+
<label>Name</label>
|
| 54 |
+
<input type="text" id="registerName" required>
|
| 55 |
+
</div>
|
| 56 |
+
<div class="form-group">
|
| 57 |
+
<label>Email</label>
|
| 58 |
+
<input type="email" id="registerEmail" required>
|
| 59 |
+
</div>
|
| 60 |
+
<div class="form-group">
|
| 61 |
+
<label>Password</label>
|
| 62 |
+
<input type="password" id="registerPassword" required>
|
| 63 |
+
</div>
|
| 64 |
+
<div id="registerError" class="error"></div>
|
| 65 |
+
<button type="submit">Register</button>
|
| 66 |
+
</form>
|
| 67 |
+
</div>
|
| 68 |
+
`;
|
| 69 |
+
|
| 70 |
+
// Tab switching
|
| 71 |
+
this.shadowRoot.querySelectorAll('.auth-tab').forEach(tab => {
|
| 72 |
+
tab.addEventListener('click', () => {
|
| 73 |
+
this.shadowRoot.querySelectorAll('.auth-tab').forEach(t => t.classList.remove('active'));
|
| 74 |
+
tab.classList.add('active');
|
| 75 |
+
document.getElementById('loginForm').classList.toggle('hidden');
|
| 76 |
+
document.getElementById('registerForm').classList.toggle('hidden');
|
| 77 |
+
});
|
| 78 |
+
});
|
| 79 |
+
|
| 80 |
+
// Form submissions
|
| 81 |
+
this.shadowRoot.getElementById('loginForm').addEventListener('submit', this.handleLogin.bind(this));
|
| 82 |
+
this.shadowRoot.getElementById('registerForm').addEventListener('submit', this.handleRegister.bind(this));
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
handleLogin(e) {
|
| 86 |
+
e.preventDefault();
|
| 87 |
+
const email = this.shadowRoot.getElementById('loginEmail').value;
|
| 88 |
+
const password = this.shadowRoot.getElementById('loginPassword').value;
|
| 89 |
+
|
| 90 |
+
// In a real app, you would call your authentication API here
|
| 91 |
+
if (email && password) {
|
| 92 |
+
localStorage.setItem('currentUser', JSON.stringify({ email }));
|
| 93 |
+
this.dispatchEvent(new CustomEvent('auth-success'));
|
| 94 |
+
} else {
|
| 95 |
+
this.shadowRoot.getElementById('loginError').textContent = 'Invalid credentials';
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
handleRegister(e) {
|
| 100 |
+
e.preventDefault();
|
| 101 |
+
const name = this.shadowRoot.getElementById('registerName').value;
|
| 102 |
+
const email = this.shadowRoot.getElementById('registerEmail').value;
|
| 103 |
+
const password = this.shadowRoot.getElementById('registerPassword').value;
|
| 104 |
+
|
| 105 |
+
// In a real app, you would call your registration API here
|
| 106 |
+
if (name && email && password) {
|
| 107 |
+
localStorage.setItem('currentUser', JSON.stringify({ email, name }));
|
| 108 |
+
this.dispatchEvent(new CustomEvent('auth-success'));
|
| 109 |
+
} else {
|
| 110 |
+
this.shadowRoot.getElementById('registerError').textContent = 'Please fill all fields';
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
customElements.define('auth-form', AuthForm);
|
components/navbar.js
CHANGED
|
@@ -50,10 +50,8 @@ class CustomNavbar extends HTMLElement {
|
|
| 50 |
<i data-feather="calendar"></i>
|
| 51 |
<span>Calendar</span>
|
| 52 |
</a>
|
| 53 |
-
<
|
| 54 |
-
|
| 55 |
-
</div>
|
| 56 |
-
</div>
|
| 57 |
</nav>
|
| 58 |
`;
|
| 59 |
}
|
|
|
|
| 50 |
<i data-feather="calendar"></i>
|
| 51 |
<span>Calendar</span>
|
| 52 |
</a>
|
| 53 |
+
<user-profile></user-profile>
|
| 54 |
+
</div>
|
|
|
|
|
|
|
| 55 |
</nav>
|
| 56 |
`;
|
| 57 |
}
|
components/task-filter.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class TaskFilter extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
.filter-container {
|
| 7 |
+
@apply mb-6 p-4 bg-white rounded-lg shadow-md;
|
| 8 |
+
}
|
| 9 |
+
.filter-row {
|
| 10 |
+
@apply flex flex-wrap items-center gap-4;
|
| 11 |
+
}
|
| 12 |
+
select, input {
|
| 13 |
+
@apply px-3 py-2 border border-gray-300 rounded-md;
|
| 14 |
+
}
|
| 15 |
+
button {
|
| 16 |
+
@apply px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700;
|
| 17 |
+
}
|
| 18 |
+
</style>
|
| 19 |
+
<div class="filter-container">
|
| 20 |
+
<div class="filter-row">
|
| 21 |
+
<select id="statusFilter">
|
| 22 |
+
<option value="all">All Statuses</option>
|
| 23 |
+
<option value="completed">Completed</option>
|
| 24 |
+
<option value="pending">Pending</option>
|
| 25 |
+
<option value="overdue">Overdue</option>
|
| 26 |
+
</select>
|
| 27 |
+
<select id="priorityFilter">
|
| 28 |
+
<option value="all">All Priorities</option>
|
| 29 |
+
<option value="high">High</option>
|
| 30 |
+
<option value="medium">Medium</option>
|
| 31 |
+
<option value="low">Low</option>
|
| 32 |
+
</select>
|
| 33 |
+
<input type="date" id="dateFilter">
|
| 34 |
+
<button id="applyFilter">Apply</button>
|
| 35 |
+
<button id="resetFilter">Reset</button>
|
| 36 |
+
</div>
|
| 37 |
+
</div>
|
| 38 |
+
`;
|
| 39 |
+
|
| 40 |
+
this.shadowRoot.getElementById('applyFilter').addEventListener('click', () => {
|
| 41 |
+
const filters = {
|
| 42 |
+
status: this.shadowRoot.getElementById('statusFilter').value,
|
| 43 |
+
priority: this.shadowRoot.getElementById('priorityFilter').value,
|
| 44 |
+
date: this.shadowRoot.getElementById('dateFilter').value
|
| 45 |
+
};
|
| 46 |
+
this.dispatchEvent(new CustomEvent('filter-changed', { detail: filters }));
|
| 47 |
+
});
|
| 48 |
+
|
| 49 |
+
this.shadowRoot.getElementById('resetFilter').addEventListener('click', () => {
|
| 50 |
+
this.shadowRoot.getElementById('statusFilter').value = 'all';
|
| 51 |
+
this.shadowRoot.getElementById('priorityFilter').value = 'all';
|
| 52 |
+
this.shadowRoot.getElementById('dateFilter').value = '';
|
| 53 |
+
this.dispatchEvent(new CustomEvent('filter-changed', { detail: {} }));
|
| 54 |
+
});
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
customElements.define('task-filter', TaskFilter);
|
components/user-profile.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class UserProfile extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
const user = JSON.parse(localStorage.getItem('currentUser'));
|
| 4 |
+
this.attachShadow({ mode: 'open' });
|
| 5 |
+
this.shadowRoot.innerHTML = `
|
| 6 |
+
<style>
|
| 7 |
+
.profile-dropdown {
|
| 8 |
+
@apply relative;
|
| 9 |
+
}
|
| 10 |
+
.profile-btn {
|
| 11 |
+
@apply flex items-center space-x-2 cursor-pointer;
|
| 12 |
+
}
|
| 13 |
+
.avatar {
|
| 14 |
+
@apply w-8 h-8 rounded-full bg-gray-200 flex items-center justify-center text-gray-600 font-medium;
|
| 15 |
+
}
|
| 16 |
+
.dropdown-menu {
|
| 17 |
+
@apply absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-10 hidden;
|
| 18 |
+
}
|
| 19 |
+
.dropdown-item {
|
| 20 |
+
@apply block px-4 py-2 text-gray-700 hover:bg-gray-100;
|
| 21 |
+
}
|
| 22 |
+
</style>
|
| 23 |
+
<div class="profile-dropdown">
|
| 24 |
+
<div class="profile-btn">
|
| 25 |
+
<div class="avatar">${user.name ? user.name.charAt(0).toUpperCase() : 'U'}</div>
|
| 26 |
+
</div>
|
| 27 |
+
<div class="dropdown-menu">
|
| 28 |
+
<a href="#" class="dropdown-item">Profile</a>
|
| 29 |
+
<a href="#" class="dropdown-item">Settings</a>
|
| 30 |
+
<a href="#" id="logoutBtn" class="dropdown-item">Logout</a>
|
| 31 |
+
</div>
|
| 32 |
+
</div>
|
| 33 |
+
`;
|
| 34 |
+
|
| 35 |
+
const profileBtn = this.shadowRoot.querySelector('.profile-btn');
|
| 36 |
+
const dropdownMenu = this.shadowRoot.querySelector('.dropdown-menu');
|
| 37 |
+
|
| 38 |
+
profileBtn.addEventListener('click', () => {
|
| 39 |
+
dropdownMenu.classList.toggle('hidden');
|
| 40 |
+
});
|
| 41 |
+
|
| 42 |
+
this.shadowRoot.getElementById('logoutBtn').addEventListener('click', (e) => {
|
| 43 |
+
e.preventDefault();
|
| 44 |
+
localStorage.removeItem('currentUser');
|
| 45 |
+
window.location.reload();
|
| 46 |
+
});
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
customElements.define('user-profile', UserProfile);
|
index.html
CHANGED
|
@@ -22,7 +22,8 @@
|
|
| 22 |
<main class="flex-1 p-8">
|
| 23 |
<div class="mb-8">
|
| 24 |
<h1 class="text-3xl font-bold text-gray-800 mb-4">Dashboard</h1>
|
| 25 |
-
<
|
|
|
|
| 26 |
<div class="bg-white rounded-xl shadow-md p-6">
|
| 27 |
<h2 class="text-xl font-semibold text-gray-700 mb-4 flex items-center">
|
| 28 |
<i data-feather="check-circle" class="mr-2 text-green-500"></i>
|
|
@@ -71,7 +72,9 @@
|
|
| 71 |
</div>
|
| 72 |
</main>
|
| 73 |
</div>
|
| 74 |
-
|
|
|
|
|
|
|
| 75 |
<script src="script.js"></script>
|
| 76 |
<script>
|
| 77 |
feather.replace();
|
|
|
|
| 22 |
<main class="flex-1 p-8">
|
| 23 |
<div class="mb-8">
|
| 24 |
<h1 class="text-3xl font-bold text-gray-800 mb-4">Dashboard</h1>
|
| 25 |
+
<task-filter></task-filter>
|
| 26 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
| 27 |
<div class="bg-white rounded-xl shadow-md p-6">
|
| 28 |
<h2 class="text-xl font-semibold text-gray-700 mb-4 flex items-center">
|
| 29 |
<i data-feather="check-circle" class="mr-2 text-green-500"></i>
|
|
|
|
| 72 |
</div>
|
| 73 |
</main>
|
| 74 |
</div>
|
| 75 |
+
<script src="components/auth-form.js"></script>
|
| 76 |
+
<script src="components/task-filter.js"></script>
|
| 77 |
+
<script src="components/user-profile.js"></script>
|
| 78 |
<script src="script.js"></script>
|
| 79 |
<script>
|
| 80 |
feather.replace();
|
script.js
CHANGED
|
@@ -1,7 +1,19 @@
|
|
| 1 |
|
| 2 |
document.addEventListener('DOMContentLoaded', function() {
|
| 3 |
-
|
| 4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
{ id: 2, title: 'Review team documents', dueDate: new Date(Date.now() + 86400000).toISOString().split('T')[0], priority: 'medium', completed: false },
|
| 6 |
{ id: 3, title: 'Schedule meeting with client', dueDate: new Date(Date.now() - 86400000).toISOString().split('T')[0], priority: 'low', completed: true }
|
| 7 |
];
|
|
@@ -18,19 +30,23 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
| 18 |
...e.detail
|
| 19 |
};
|
| 20 |
tasks.push(newTask);
|
| 21 |
-
localStorage.setItem(
|
| 22 |
-
|
| 23 |
updateKpiReport();
|
| 24 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
// Complete task
|
| 27 |
-
|
| 28 |
const taskId = parseInt(e.detail.taskId);
|
| 29 |
tasks = tasks.map(task =>
|
| 30 |
task.id === taskId ? {...task, completed: !task.completed} : task
|
| 31 |
);
|
| 32 |
-
localStorage.setItem(
|
| 33 |
-
|
| 34 |
updateKpiReport();
|
| 35 |
});
|
| 36 |
const documents = [
|
|
@@ -38,11 +54,37 @@ const documents = [
|
|
| 38 |
{ id: 2, title: 'Meeting Notes', type: 'doc', lastModified: '2023-06-03', size: '1.1 MB' },
|
| 39 |
{ id: 3, title: 'Budget Plan', type: 'xls', lastModified: '2023-05-28', size: '3.7 MB' }
|
| 40 |
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
|
|
|
|
|
|
| 46 |
taskCard.setAttribute('task-id', task.id);
|
| 47 |
taskCard.setAttribute('task-title', task.title);
|
| 48 |
taskCard.setAttribute('due-date', task.dueDate);
|
|
|
|
| 1 |
|
| 2 |
document.addEventListener('DOMContentLoaded', function() {
|
| 3 |
+
// Check authentication
|
| 4 |
+
const currentUser = JSON.parse(localStorage.getItem('currentUser'));
|
| 5 |
+
if (!currentUser) {
|
| 6 |
+
document.body.innerHTML = '<auth-form></auth-form>';
|
| 7 |
+
document.querySelector('auth-form').addEventListener('auth-success', () => {
|
| 8 |
+
window.location.reload();
|
| 9 |
+
});
|
| 10 |
+
return;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
// Initialize tasks for current user
|
| 14 |
+
const userTasksKey = `tasks_${currentUser.email}`;
|
| 15 |
+
let tasks = JSON.parse(localStorage.getItem(userTasksKey)) || [
|
| 16 |
+
{ id: 1, title: 'Complete project proposal', dueDate: new Date().toISOString().split('T')[0], priority: 'high', completed: false },
|
| 17 |
{ id: 2, title: 'Review team documents', dueDate: new Date(Date.now() + 86400000).toISOString().split('T')[0], priority: 'medium', completed: false },
|
| 18 |
{ id: 3, title: 'Schedule meeting with client', dueDate: new Date(Date.now() - 86400000).toISOString().split('T')[0], priority: 'low', completed: true }
|
| 19 |
];
|
|
|
|
| 30 |
...e.detail
|
| 31 |
};
|
| 32 |
tasks.push(newTask);
|
| 33 |
+
localStorage.setItem(userTasksKey, JSON.stringify(tasks));
|
| 34 |
+
renderTasks();
|
| 35 |
updateKpiReport();
|
| 36 |
});
|
| 37 |
+
// Handle task filtering
|
| 38 |
+
document.addEventListener('filter-changed', (e) => {
|
| 39 |
+
renderTasks(e.detail);
|
| 40 |
+
});
|
| 41 |
|
| 42 |
// Complete task
|
| 43 |
+
document.addEventListener('task-completed', (e) => {
|
| 44 |
const taskId = parseInt(e.detail.taskId);
|
| 45 |
tasks = tasks.map(task =>
|
| 46 |
task.id === taskId ? {...task, completed: !task.completed} : task
|
| 47 |
);
|
| 48 |
+
localStorage.setItem(userTasksKey, JSON.stringify(tasks));
|
| 49 |
+
renderTasks();
|
| 50 |
updateKpiReport();
|
| 51 |
});
|
| 52 |
const documents = [
|
|
|
|
| 54 |
{ id: 2, title: 'Meeting Notes', type: 'doc', lastModified: '2023-06-03', size: '1.1 MB' },
|
| 55 |
{ id: 3, title: 'Budget Plan', type: 'xls', lastModified: '2023-05-28', size: '3.7 MB' }
|
| 56 |
];
|
| 57 |
+
// Filter and render tasks
|
| 58 |
+
function filterTasks(tasks, filters = {}) {
|
| 59 |
+
return tasks.filter(task => {
|
| 60 |
+
let matches = true;
|
| 61 |
+
|
| 62 |
+
if (filters.status === 'completed') {
|
| 63 |
+
matches = matches && task.completed;
|
| 64 |
+
} else if (filters.status === 'pending') {
|
| 65 |
+
matches = matches && !task.completed && new Date(task.dueDate) >= new Date();
|
| 66 |
+
} else if (filters.status === 'overdue') {
|
| 67 |
+
matches = matches && !task.completed && new Date(task.dueDate) < new Date();
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
if (filters.priority && filters.priority !== 'all') {
|
| 71 |
+
matches = matches && task.priority === filters.priority;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
if (filters.date) {
|
| 75 |
+
matches = matches && task.dueDate === filters.date;
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
return matches;
|
| 79 |
+
});
|
| 80 |
+
}
|
| 81 |
|
| 82 |
+
function renderTasks(filters = {}) {
|
| 83 |
+
const filteredTasks = filterTasks(tasks, filters);
|
| 84 |
+
const tasksContainer = document.getElementById('tasks-container');
|
| 85 |
+
tasksContainer.innerHTML = '';
|
| 86 |
+
filteredTasks.forEach(task => {
|
| 87 |
+
const taskCard = document.createElement('custom-task-card');
|
| 88 |
taskCard.setAttribute('task-id', task.id);
|
| 89 |
taskCard.setAttribute('task-title', task.title);
|
| 90 |
taskCard.setAttribute('due-date', task.dueDate);
|