ttvtlb commited on
Commit
ab59162
·
verified ·
1 Parent(s): 62720b9

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 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
- <div class="user-profile">
54
- <div class="avatar">JD</div>
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
- <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
 
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
- let tasks = JSON.parse(localStorage.getItem('tasks')) || [
4
- { id: 1, title: 'Complete project proposal', dueDate: new Date().toISOString().split('T')[0], priority: 'high', completed: false },
 
 
 
 
 
 
 
 
 
 
 
 
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('tasks', JSON.stringify(tasks));
22
- renderTasks();
23
  updateKpiReport();
24
  });
 
 
 
 
25
 
26
  // Complete task
27
- document.addEventListener('task-completed', (e) => {
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('tasks', JSON.stringify(tasks));
33
- renderTasks();
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
- // Render tasks
43
- const tasksContainer = document.getElementById('tasks-container');
44
- tasks.forEach(task => {
45
- const taskCard = document.createElement('custom-task-card');
 
 
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);