ttvtlb commited on
Commit
9092603
·
verified ·
1 Parent(s): 4de453a

Xây dựng một hệ thống đầy đủ và hoàn chỉnh quản lý KPI, nhân viên có thể cập nhật KPI thực hiện hàng ngày, tổ trưởng cũng có thể cập nhật cho nhân viên trong tổ thống kê và xem trong tổ, admin quản lý tất cả bao gồm thống kê, xuất excel.

Browse files
components/kpi-form.js ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class KpiForm extends HTMLElement {
2
+ connectedCallback() {
3
+ this.attachShadow({ mode: 'open' });
4
+ this.shadowRoot.innerHTML = `
5
+ <style>
6
+ .kpi-form {
7
+ @apply bg-white rounded-lg shadow-md p-6 mb-6;
8
+ }
9
+ .form-title {
10
+ @apply text-xl font-semibold text-gray-800 mb-4;
11
+ }
12
+ .form-group {
13
+ @apply mb-4;
14
+ }
15
+ label {
16
+ @apply block text-sm font-medium text-gray-700 mb-1;
17
+ }
18
+ input, select, textarea {
19
+ @apply w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500;
20
+ }
21
+ .submit-btn {
22
+ @apply w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-md transition-colors duration-200;
23
+ }
24
+ </style>
25
+ <div class="kpi-form">
26
+ <h3 class="form-title">Update Daily KPI</h3>
27
+ <form id="kpiForm">
28
+ <div class="form-group">
29
+ <label for="kpiDate">Date</label>
30
+ <input type="date" id="kpiDate" required>
31
+ </div>
32
+ <div class="form-group">
33
+ <label for="kpiMetric">KPI Metric</label>
34
+ <select id="kpiMetric" required>
35
+ <option value="">Select Metric</option>
36
+ <option value="productivity">Productivity</option>
37
+ <option value="quality">Quality</option>
38
+ <option value="efficiency">Efficiency</option>
39
+ <option value="attendance">Attendance</option>
40
+ </select>
41
+ </div>
42
+ <div class="form-group">
43
+ <label for="kpiValue">Value</label>
44
+ <input type="number" id="kpiValue" min="0" max="100" required>
45
+ </div>
46
+ <div class="form-group">
47
+ <label for="kpiNotes">Notes</label>
48
+ <textarea id="kpiNotes" rows="3"></textarea>
49
+ </div>
50
+ <button type="submit" class="submit-btn">Submit KPI</button>
51
+ </form>
52
+ </div>
53
+ `;
54
+
55
+ this.shadowRoot.getElementById('kpiForm').addEventListener('submit', (e) => {
56
+ e.preventDefault();
57
+ const newKpi = {
58
+ date: this.shadowRoot.getElementById('kpiDate').value,
59
+ metric: this.shadowRoot.getElementById('kpiMetric').value,
60
+ value: parseFloat(this.shadowRoot.getElementById('kpiValue').value),
61
+ notes: this.shadowRoot.getElementById('kpiNotes').value,
62
+ timestamp: new Date().toISOString()
63
+ };
64
+ this.dispatchEvent(new CustomEvent('kpi-submitted', { detail: newKpi }));
65
+ this.shadowRoot.getElementById('kpiForm').reset();
66
+ });
67
+ }
68
+ }
69
+
70
+ customElements.define('kpi-form', KpiForm);
components/kpi-team-view.js ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```javascript
2
+ class KpiTeamView extends HTMLElement {
3
+ connectedCallback() {
4
+ this.attachShadow({ mode: 'open' });
5
+ this.shadowRoot.innerHTML = `
6
+ <style>
7
+ .team-view {
8
+ @apply bg-white rounded-lg shadow-md p-6;
9
+ }
10
+ .view-header {
11
+ @apply flex justify-between items-center mb-6;
12
+ }
13
+ .view-title {
14
+ @apply text-xl font-semibold text-gray-800;
15
+ }
16
+ .export-btn {
17
+ @apply bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-md text-sm font-medium;
18
+ }
19
+ .team-table {
20
+ @apply w-full border-collapse;
21
+ }
22
+ .team-table th {
23
+ @apply text-left py-3 px-4 bg-gray-50 text-gray-600 text-sm font-medium;
24
+ }
25
+ .team-table td {
26
+ @apply py-3 px-4 border-b border-gray-200;
27
+ }
28
+ .progress-bar {
29
+ @apply h-2 bg-gray-200 rounded-full overflow-hidden;
30
+ }
31
+ .progress-value {
32
+ @apply h-full;
33
+ }
34
+ .good {
35
+ @apply bg-green-500;
36
+ }
37
+ .average {
38
+ @apply bg-yellow-500;
39
+ }
40
+ .poor {
41
+ @apply bg-red-500;
42
+ }
43
+ </style>
44
+ <div class="team-view">
45
+ <div class="view-header">
46
+ <h3 class="view-title">Team KPI Overview</h3>
47
+ <button class="export-btn" id="exportExcel">
48
+ <i data-feather="download"></i> Export to Excel
49
+ </button>
50
+ </div>
51
+ <table class="team-table">
52
+ <thead>
53
+ <tr>
54
+ <th>Employee</th>
55
+ <th>Productivity</th>
56
+ <th>Quality</th>
57
+ <th>Efficiency</th>
58
+ <th>Attendance</th>
59
+ <th>Overall</th>
60
+ </tr>
61
+ </thead>
62
+ <tbody id="teamData">
63
+ <!-- Team data will be populated here -->
components/navbar.js CHANGED
@@ -50,8 +50,10 @@ class CustomNavbar extends HTMLElement {
50
  <i data-feather="calendar"></i>
51
  <span>Calendar</span>
52
  </a>
53
- <user-profile></user-profile>
54
- </div>
 
 
55
  </nav>
56
  `;
57
  }
 
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
  }
index.html CHANGED
@@ -22,8 +22,7 @@
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,9 +71,7 @@
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();
 
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
  </div>
72
  </main>
73
  </div>
74
+
 
 
75
  <script src="script.js"></script>
76
  <script>
77
  feather.replace();
script.js CHANGED
@@ -1,19 +1,7 @@
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,23 +18,19 @@ document.addEventListener('DOMContentLoaded', function() {
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,37 +38,11 @@ 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);
 
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
  ...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
  { 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);