ttvtlb commited on
Commit
63fb39f
·
verified ·
1 Parent(s): 297e12f

Xây dựng một hệ thống đầy đủ, hoàn chỉnh việc quản lý KPI của đơn vị, người dùng đăng nhập và cập nhật KPI thực hiện hành ngày, tổ trưỡng có thể cập nhật KPI của nhân viên, quản lý KPI trong tổ, Admin quản lý tất cả, cập nhật danh sách KPI, thống kê, xuất dữ liệu ra excel

Browse files
Files changed (5) hide show
  1. README.md +8 -5
  2. components/auth-form.js +165 -0
  3. index.html +18 -19
  4. script.js +54 -0
  5. style.css +20 -18
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Kpi Tracker Pro Performance Pulse
3
- emoji: 📚
4
- colorFrom: blue
5
- colorTo: green
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: KPI Tracker Pro - Performance Pulse 💼
3
+ colorFrom: gray
4
+ colorTo: red
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://huggingface.co/deepsite).
components/auth-form.js ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class AuthForm extends HTMLElement {
2
+ connectedCallback() {
3
+ this.attachShadow({ mode: 'open' });
4
+ this.shadowRoot.innerHTML = `
5
+ <style>
6
+ .auth-container {
7
+ background: white;
8
+ border-radius: 0.75rem;
9
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
10
+ width: 100%;
11
+ max-width: 28rem;
12
+ padding: 2rem;
13
+ }
14
+ .auth-title {
15
+ font-size: 1.5rem;
16
+ font-weight: 600;
17
+ margin-bottom: 1.5rem;
18
+ color: #1a365d;
19
+ text-align: center;
20
+ }
21
+ .input-group {
22
+ margin-bottom: 1.25rem;
23
+ }
24
+ .input-label {
25
+ display: block;
26
+ margin-bottom: 0.5rem;
27
+ font-size: 0.875rem;
28
+ font-weight: 500;
29
+ color: #4a5568;
30
+ }
31
+ .input-field {
32
+ width: 100%;
33
+ padding: 0.75rem;
34
+ border: 1px solid #e2e8f0;
35
+ border-radius: 0.375rem;
36
+ font-size: 0.875rem;
37
+ transition: border-color 0.2s;
38
+ }
39
+ .input-field:focus {
40
+ outline: none;
41
+ border-color: #4299e1;
42
+ box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.2);
43
+ }
44
+ .submit-btn {
45
+ width: 100%;
46
+ padding: 0.75rem;
47
+ background-color: #4299e1;
48
+ color: white;
49
+ font-weight: 500;
50
+ border: none;
51
+ border-radius: 0.375rem;
52
+ cursor: pointer;
53
+ transition: background-color 0.2s;
54
+ }
55
+ .submit-btn:hover {
56
+ background-color: #3182ce;
57
+ }
58
+ .toggle-auth {
59
+ text-align: center;
60
+ margin-top: 1rem;
61
+ font-size: 0.875rem;
62
+ color: #4a5568;
63
+ }
64
+ .toggle-link {
65
+ color: #4299e1;
66
+ font-weight: 500;
67
+ cursor: pointer;
68
+ }
69
+ .error-message {
70
+ color: #e53e3e;
71
+ font-size: 0.875rem;
72
+ margin-top: 0.25rem;
73
+ }
74
+ </style>
75
+ <div class="auth-container">
76
+ <h1 class="auth-title">KPI Tracker Pro</h1>
77
+ <form id="loginForm">
78
+ <div class="input-group">
79
+ <label class="input-label" for="email">Email</label>
80
+ <input type="email" id="email" class="input-field" placeholder="your@email.com" required>
81
+ </div>
82
+ <div class="input-group">
83
+ <label class="input-label" for="password">Password</label>
84
+ <input type="password" id="password" class="input-field" placeholder="••••••••" required>
85
+ </div>
86
+ <button type="submit" class="submit-btn">Sign In</button>
87
+ </form>
88
+ <div class="toggle-auth">
89
+ <span>Don't have an account? </span>
90
+ <span class="toggle-link" id="toggleRegister">Register</span>
91
+ </div>
92
+ <div id="errorMessage" class="error-message" style="display: none;"></div>
93
+ </div>
94
+ `;
95
+
96
+ this.shadowRoot.getElementById('loginForm').addEventListener('submit', this.handleLogin.bind(this));
97
+ this.shadowRoot.getElementById('toggleRegister').addEventListener('click', () => {
98
+ this.toggleAuthMode('register');
99
+ });
100
+ }
101
+
102
+ handleLogin(e) {
103
+ e.preventDefault();
104
+ const email = this.shadowRoot.getElementById('email').value;
105
+ const password = this.shadowRoot.getElementById('password').value;
106
+
107
+ // Mock authentication - in a real app, this would be an API call
108
+ if (email === 'admin@kpi.com' && password === 'admin123') {
109
+ setCurrentUser({ email, role: 'admin', name: 'Admin User' });
110
+ window.location.href = '/dashboard.html';
111
+ } else if (email === 'manager@kpi.com' && password === 'manager123') {
112
+ setCurrentUser({ email, role: 'manager', name: 'Team Manager', team: 'Sales Team' });
113
+ window.location.href = '/dashboard.html';
114
+ } else if (email === 'employee@kpi.com' && password === 'employee123') {
115
+ setCurrentUser({ email, role: 'employee', name: 'John Doe', manager: 'manager@kpi.com' });
116
+ window.location.href = '/dashboard.html';
117
+ } else {
118
+ const errorElement = this.shadowRoot.getElementById('errorMessage');
119
+ errorElement.textContent = 'Invalid email or password';
120
+ errorElement.style.display = 'block';
121
+ }
122
+ }
123
+
124
+ toggleAuthMode(mode) {
125
+ const form = this.shadowRoot.getElementById('loginForm');
126
+ const toggleText = this.shadowRoot.querySelector('.toggle-auth span:first-child');
127
+ const toggleLink = this.shadowRoot.getElementById('toggleRegister');
128
+ const title = this.shadowRoot.querySelector('.auth-title');
129
+
130
+ if (mode === 'register') {
131
+ title.textContent = 'Create Account';
132
+ form.innerHTML = `
133
+ <div class="input-group">
134
+ <label class="input-label" for="regName">Full Name</label>
135
+ <input type="text" id="regName" class="input-field" placeholder="John Doe" required>
136
+ </div>
137
+ <div class="input-group">
138
+ <label class="input-label" for="regEmail">Email</label>
139
+ <input type="email" id="regEmail" class="input-field" placeholder="your@email.com" required>
140
+ </div>
141
+ <div class="input-group">
142
+ <label class="input-label" for="regPassword">Password</label>
143
+ <input type="password" id="regPassword" class="input-field" placeholder="••••••••" required>
144
+ </div>
145
+ <div class="input-group">
146
+ <label class="input-label" for="regConfirmPassword">Confirm Password</label>
147
+ <input type="password" id="regConfirmPassword" class="input-field" placeholder="••••••••" required>
148
+ </div>
149
+ <div class="input-group">
150
+ <label class="input-label" for="regRole">Role</label>
151
+ <select id="regRole" class="input-field" required>
152
+ <option value="">Select your role</option>
153
+ <option value="employee">Employee</option>
154
+ <option value="manager">Manager</option>
155
+ </select>
156
+ </div>
157
+ <button type="submit" class="submit-btn">Register</button>
158
+ `;
159
+ toggleText.textContent = 'Already have an account? ';
160
+ toggleLink.textContent = 'Sign In';
161
+ toggleLink.id = 'toggleLogin';
162
+ toggleLink.addEventListener('click', () => {
163
+ this.toggleAuthMode('login');
164
+ });
165
+ form.addEventListener('submit', this.handleRegister.bind
index.html CHANGED
@@ -1,19 +1,18 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>KPI Tracker Pro - Login</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
10
+ <script src="https://unpkg.com/feather-icons"></script>
11
+ <script src="components/auth-form.js"></script>
12
+ </head>
13
+ <body class="bg-gray-50 min-h-screen flex items-center justify-center">
14
+ <auth-form></auth-form>
15
+ <script>feather.replace();</script>
16
+ <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
17
+ </body>
18
+ </html>
 
script.js ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Shared utility functions
2
+ const formatDate = (dateString) => {
3
+ const options = { year: 'numeric', month: 'short', day: 'numeric' };
4
+ return new Date(dateString).toLocaleDateString(undefined, options);
5
+ };
6
+
7
+ const calculateProgress = (current, target) => {
8
+ return Math.min(Math.round((current / target) * 100), 100);
9
+ };
10
+
11
+ // Auth state management
12
+ let currentUser = null;
13
+
14
+ const setCurrentUser = (user) => {
15
+ currentUser = user;
16
+ localStorage.setItem('currentUser', JSON.stringify(user));
17
+ updateUIForUserRole();
18
+ };
19
+
20
+ const getCurrentUser = () => {
21
+ if (!currentUser && localStorage.getItem('currentUser')) {
22
+ currentUser = JSON.parse(localStorage.getItem('currentUser'));
23
+ }
24
+ return currentUser;
25
+ };
26
+
27
+ const logout = () => {
28
+ currentUser = null;
29
+ localStorage.removeItem('currentUser');
30
+ window.location.href = '/';
31
+ };
32
+
33
+ const updateUIForUserRole = () => {
34
+ const user = getCurrentUser();
35
+ if (!user) return;
36
+
37
+ // Show/hide elements based on role
38
+ document.querySelectorAll('[data-role="admin"]').forEach(el => {
39
+ el.style.display = user.role === 'admin' ? 'block' : 'none';
40
+ });
41
+
42
+ document.querySelectorAll('[data-role="manager"]').forEach(el => {
43
+ el.style.display = user.role === 'manager' ? 'block' : 'none';
44
+ });
45
+
46
+ document.querySelectorAll('[data-role="employee"]').forEach(el => {
47
+ el.style.display = user.role === 'employee' ? 'block' : 'none';
48
+ });
49
+ };
50
+
51
+ // Initialize app when DOM is loaded
52
+ document.addEventListener('DOMContentLoaded', () => {
53
+ updateUIForUserRole();
54
+ });
style.css CHANGED
@@ -1,28 +1,30 @@
 
 
1
  body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
 
 
28
  }
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
2
+
3
  body {
4
+ font-family: 'Inter', sans-serif;
 
5
  }
6
 
7
+ .kpi-card {
8
+ transition: all 0.3s ease;
 
9
  }
10
 
11
+ .kpi-card:hover {
12
+ transform: translateY(-5px);
13
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1);
 
 
14
  }
15
 
16
+ .progress-bar {
17
+ transition: width 1s ease-in-out;
 
 
 
 
18
  }
19
 
20
+ .dashboard-grid {
21
+ display: grid;
22
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
23
+ gap: 1.5rem;
24
  }
25
+
26
+ @media (max-width: 768px) {
27
+ .dashboard-grid {
28
+ grid-template-columns: 1fr;
29
+ }
30
+ }