alterzick commited on
Commit
a94002f
·
verified ·
1 Parent(s): 3266bf9

Add 3 files

Browse files
Files changed (3) hide show
  1. README.md +7 -5
  2. index.html +1010 -19
  3. prompts.txt +5 -0
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Https Huggingface Co Spaces Alterzick Daily Job Tracker V1
3
- emoji: 📊
4
- colorFrom: red
5
- colorTo: blue
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: https-huggingface-co-spaces-alterzick-daily-job-tracker-v1
3
+ emoji: ⚛️
4
+ colorFrom: gray
5
+ colorTo: pink
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - QwenSite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,1010 @@
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="id">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>Daily Job Tracker</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
9
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
11
+ <style>
12
+ body {
13
+ font-family: 'Inter', sans-serif;
14
+ }
15
+ .chart-container {
16
+ position: relative;
17
+ height: 300px;
18
+ width: 100%;
19
+ }
20
+ .filter-badge {
21
+ display: inline-flex;
22
+ align-items: center;
23
+ padding: 0.375rem 0.75rem;
24
+ border-radius: 9999px;
25
+ font-size: 0.875rem;
26
+ font-weight: 500;
27
+ margin-right: 0.5rem;
28
+ margin-bottom: 0.5rem;
29
+ }
30
+ .attachment-item {
31
+ display: flex;
32
+ align-items: center;
33
+ padding: 0.5rem;
34
+ background-color: #f7f7f7;
35
+ border-radius: 0.375rem;
36
+ margin-bottom: 0.5rem;
37
+ word-break: break-all;
38
+ }
39
+ .attachment-item a {
40
+ color: #4f46e5;
41
+ text-decoration: underline;
42
+ margin-left: 0.5rem;
43
+ }
44
+ .attachment-item button {
45
+ color: #ef4444;
46
+ margin-left: 0.5rem;
47
+ }
48
+ .pagination {
49
+ @apply flex justify-center mt-4 space-x-1;
50
+ }
51
+ .pagination button {
52
+ @apply px-3 py-1 border rounded text-sm hover:bg-gray-100;
53
+ }
54
+ .pagination button.active {
55
+ @apply bg-indigo-600 text-white;
56
+ }
57
+ </style>
58
+ </head>
59
+ <body class="bg-gray-50 text-gray-800">
60
+
61
+ <div class="max-w-5xl mx-auto p-5">
62
+ <header class="text-center my-6">
63
+ <h1 class="text-3xl font-bold text-indigo-600">📅 Daily Job Tracker</h1>
64
+ <p class="text-gray-600 mt-2">Lacak, review, dan pantau produktivitas harian Anda</p>
65
+ </header>
66
+
67
+ <!-- Tabs Navigation -->
68
+ <div class="flex border-b border-gray-300 mb-6 overflow-x-auto">
69
+ <button onclick="showTab('input')" class="px-6 py-3 font-medium text-gray-700 border-b-2 border-indigo-500 bg-gray-100">➕ Input Job</button>
70
+ <button onclick="showTab('history')" class="px-6 py-3 text-gray-600 hover:text-indigo-600">📋 Riwayat</button>
71
+ <button onclick="showTab('review')" class="px-6 py-3 text-gray-600 hover:text-indigo-600">✅ Review</button>
72
+ <button onclick="showTab('chart')" class="px-6 py-3 text-gray-600 hover:text-indigo-600">📊 Grafik</button>
73
+ </div>
74
+
75
+ <!-- Input Tab -->
76
+ <div id="input" class="tab-content">
77
+ <div class="bg-white p-6 rounded-lg shadow mb-8">
78
+ <h2 class="text-xl font-semibold mb-4">Catat Pekerjaan Hari Ini</h2>
79
+ <form id="jobForm" class="space-y-4">
80
+ <div>
81
+ <label class="block text-sm font-medium text-gray-600">Tanggal</label>
82
+ <input type="date" id="jobDate" class="mt-1 block w-full border border-gray-300 rounded-md p-2" required />
83
+ </div>
84
+ <div>
85
+ <label class="block text-sm font-medium text-gray-600">Kegiatan</label>
86
+ <textarea id="jobTask" rows="3" class="mt-1 block w-full border border-gray-300 rounded-md p-2" placeholder="Tulis tugas yang telah dikerjakan..." required></textarea>
87
+ </div>
88
+ <div class="flex gap-4">
89
+ <div class="w-1/2">
90
+ <label class="block text-sm font-medium text-gray-600">Status</label>
91
+ <select id="jobStatus" class="mt-1 block w-full border border-gray-300 rounded-md p-2">
92
+ <option value="Selesai">Selesai</option>
93
+ <option value="Dalam Proses">Dalam Proses</option>
94
+ <option value="Belum Mulai">Belum Mulai</option>
95
+ </select>
96
+ </div>
97
+ <div class="w-1/2">
98
+ <label class="block text-sm font-medium text-gray-600">Kategori</label>
99
+ <select id="jobCategory" class="mt-1 block w-full border border-gray-300 rounded-md p-2">
100
+ <option value="Pekerjaan">Pekerjaan</option>
101
+ <option value="Pribadi">Pribadi</option>
102
+ <option value="Belajar">Belajar</option>
103
+ <option value="Lainnya">Lainnya</option>
104
+ </select>
105
+ </div>
106
+ </div>
107
+ <div>
108
+ <label class="block text-sm font-medium text-gray-600">Lampiran</label>
109
+ <input type="file" id="jobAttachment" class="mt-1 block w-full border border-gray-300 rounded-md p-2" />
110
+ <div id="attachmentList" class="mt-2"></div>
111
+ </div>
112
+ <button type="submit" class="bg-indigo-600 text-white px-5 py-2 rounded hover:bg-indigo-700 transition">➕ Tambahkan</button>
113
+ </form>
114
+
115
+ <!-- Import Excel Section -->
116
+ <div class="mt-8 p-6 border-t border-gray-200">
117
+ <h3 class="text-lg font-semibold mb-4">📥 Impor Data dari Excel</h3>
118
+ <p class="text-gray-600 text-sm mb-4">Upload file Excel (.xlsx) dengan format kolom: Tanggal, Kegiatan, Status, Kategori</p>
119
+ <p class="text-blue-600 text-sm mb-4">Anda dapat mengimpor data tanpa batas. Semua data akan ditambahkan ke dalam sistem.</p>
120
+ <div class="flex items-center">
121
+ <input type="file" id="excelFile" accept=".xlsx" class="p-2 border border-gray-300 rounded flex-1" />
122
+ <button onclick="importFromExcel()" class="ml-2 bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700">
123
+ <span id="importProgressText">Import</span>
124
+ <span id="loadingSpinner" class="hidden">
125
+ <svg class="inline w-5 h-5 mr-2 text-white animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
126
+ <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
127
+ <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
128
+ </svg>
129
+ Memproses...
130
+ </span>
131
+ </button>
132
+ </div>
133
+ <p id="importStatus" class="mt-2 text-sm"></p>
134
+ </div>
135
+ </div>
136
+ </div>
137
+
138
+ <!-- History Tab -->
139
+ <div id="history" class="tab-content hidden">
140
+ <div class="bg-white p-6 rounded-lg shadow mb-8">
141
+ <h2 class="text-xl font-semibold mb-4">📅 Riwayat Pekerjaan</h2>
142
+ <div class="flex flex-col sm:flex-row gap-4 mb-4 items-start">
143
+ <input type="text" id="searchInput" placeholder="Cari tugas..." class="p-2 border border-gray-300 rounded flex-1" oninput="filterJobs()" />
144
+ <input type="date" id="filterDate" class="p-2 border border-gray-300 rounded w-full sm:w-48" oninput="filterJobs()" />
145
+ <button onclick="exportToExcel()" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 whitespace-nowrap">
146
+ 📤 Ekspor ke Excel
147
+ </button>
148
+ </div>
149
+ <div id="historyList" class="space-y-3 max-h-96 overflow-y-auto"></div>
150
+ <div id="paginationContainer" class="pagination mt-4"></div>
151
+ </div>
152
+ </div>
153
+
154
+ <!-- Review Tab -->
155
+ <div id="review" class="tab-content hidden">
156
+ <div class="bg-white p-6 rounded-lg shadow mb-8">
157
+ <h2 class="text-xl font-semibold mb-4">✅ Review Harian</h2>
158
+ <p class="text-gray-600 mb-4">Tinjau tugas-tugas Anda berdasarkan status.</p>
159
+ <div class="flex flex-col sm:flex-row gap-4 mb-4">
160
+ <select id="statusFilter" class="p-2 border border-gray-300 rounded flex-1" onchange="filterReview()">
161
+ <option value="">Semua Status</option>
162
+ <option value="Selesai">Selesai</option>
163
+ <option value="Dalam Proses">Dalam Proses</option>
164
+ <option value="Belum Mulai">Belum Mulai</option>
165
+ </select>
166
+ <input type="date" id="reviewStartDate" class="p-2 border border-gray-300 rounded w-full sm:w-32" oninput="filterReview()" />
167
+ <span class="self-center">sampai</span>
168
+ <input type="date" id="reviewEndDate" class="p-2 border border-gray-300 rounded w-full sm:w-32" oninput="filterReview()" />
169
+ <button onclick="exportReviewToExcel()" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 whitespace-nowrap">
170
+ 📤 Ekspor ke Excel
171
+ </button>
172
+ </div>
173
+ <div class="flex flex-wrap mb-4">
174
+ <div id="activeFilters" class="flex flex-wrap"></div>
175
+ </div>
176
+ <div id="reviewList" class="space-y-3"></div>
177
+ </div>
178
+ </div>
179
+
180
+ <!-- Chart Tab -->
181
+ <div id="chart" class="tab-content hidden">
182
+ <div class="bg-white p-6 rounded-lg shadow mb-8">
183
+ <h2 class="text-xl font-semibold mb-4">📊 Grafik Produktivitas</h2>
184
+ <p class="text-gray-600 mb-4">Analisis produktivitas Anda dalam rentang tanggal tertentu.</p>
185
+ <div class="flex flex-col sm:flex-row gap-4 mb-6">
186
+ <input type="date" id="chartStartDate" class="p-2 border border-gray-300 rounded w-full sm:w-1/2" oninput="filterChart()" />
187
+ <span class="self-center">sampai</span>
188
+ <input type="date" id="chartEndDate" class="p-2 border border-gray-300 rounded w-full sm:w-1/2" oninput="filterChart()" />
189
+ </div>
190
+ <div class="chart-container">
191
+ <canvas id="productivityChart"></canvas>
192
+ </div>
193
+ </div>
194
+ </div>
195
+
196
+ </div>
197
+
198
+ <script>
199
+ // Pagination variables
200
+ const ITEMS_PER_PAGE = 10;
201
+ let currentPage = 1;
202
+
203
+ // Default tab
204
+ function showTab(tabId) {
205
+ document.querySelectorAll('.tab-content').forEach(tab => {
206
+ tab.classList.add('hidden');
207
+ });
208
+ document.getElementById(tabId).classList.remove('hidden');
209
+ if (tabId === 'chart') filterChart();
210
+ if (tabId === 'history') {
211
+ currentPage = 1;
212
+ filterJobs();
213
+ }
214
+ if (tabId === 'review') filterReview();
215
+ }
216
+
217
+ // Data Management
218
+ function getJobs() {
219
+ return JSON.parse(localStorage.getItem('dailyJobs') || '[]');
220
+ }
221
+
222
+ function saveJobs(jobs) {
223
+ localStorage.setItem('dailyJobs', JSON.stringify(jobs));
224
+ }
225
+
226
+ function addJob(job) {
227
+ const jobs = getJobs();
228
+ jobs.push(job);
229
+ saveJobs(jobs);
230
+ filterJobs();
231
+ filterReview();
232
+ filterChart();
233
+ alert('Tugas berhasil ditambahkan!');
234
+ }
235
+
236
+ // Input Form Handling
237
+ document.getElementById('jobForm').addEventListener('submit', function(e) {
238
+ e.preventDefault();
239
+ const date = document.getElementById('jobDate').value;
240
+ const task = document.getElementById('jobTask').value;
241
+ const status = document.getElementById('jobStatus').value;
242
+ const category = document.getElementById('jobCategory').value;
243
+
244
+ // Attachments processing
245
+ const attachmentInput = document.getElementById('jobAttachment');
246
+ const attachments = [];
247
+
248
+ if (attachmentInput.files.length > 0) {
249
+ for (let i = 0; i < attachmentInput.files.length; i++) {
250
+ const file = attachmentInput.files[i];
251
+ attachments.push({
252
+ name: file.name,
253
+ type: file.type,
254
+ size: file.size,
255
+ // In a real app, this would be the URL to the uploaded file
256
+ url: `data/${file.name}`
257
+ });
258
+ }
259
+ }
260
+
261
+ const job = {
262
+ id: Date.now(),
263
+ date,
264
+ task,
265
+ status,
266
+ category,
267
+ attachments: attachments,
268
+ timestamp: new Date().toISOString()
269
+ };
270
+
271
+ addJob(job);
272
+ this.reset();
273
+ document.getElementById('attachmentList').innerHTML = '';
274
+ });
275
+
276
+ // Handle attachment display
277
+ document.getElementById('jobAttachment').addEventListener('change', function(e) {
278
+ const attachmentList = document.getElementById('attachmentList');
279
+ attachmentList.innerHTML = '';
280
+
281
+ if (this.files.length > 0) {
282
+ for (let i = 0; i < this.files.length; i++) {
283
+ const file = this.files[i];
284
+ const attachmentItem = document.createElement('div');
285
+ attachmentItem.className = 'attachment-item';
286
+ attachmentItem.innerHTML = `
287
+ <span>📎 ${file.name} (${formatFileSize(file.size)})</span>
288
+ `;
289
+ attachmentList.appendChild(attachmentItem);
290
+ }
291
+ }
292
+ });
293
+
294
+ // Format file size for display
295
+ function formatFileSize(bytes) {
296
+ if (bytes === 0) return '0 Bytes';
297
+ const k = 1024;
298
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
299
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
300
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
301
+ }
302
+
303
+ // Import from Excel
304
+ function importFromExcel() {
305
+ // Get elements
306
+ const fileInput = document.getElementById('excelFile');
307
+ const statusElement = document.getElementById('importStatus');
308
+ const importProgressText = document.getElementById('importProgressText');
309
+ const loadingSpinner = document.getElementById('loadingSpinner');
310
+
311
+ // Validate file selection
312
+ if (!fileInput.files.length) {
313
+ statusElement.textContent = 'Silakan pilih file terlebih dahulu.';
314
+ statusElement.className = 'text-red-500';
315
+ return;
316
+ }
317
+
318
+ // Show loading state
319
+ importProgressText.classList.add('hidden');
320
+ loadingSpinner.classList.remove('hidden');
321
+
322
+ // Reset status
323
+ statusElement.textContent = 'Memulai proses impor...';
324
+ statusElement.className = 'text-blue-500';
325
+
326
+ // Process file in background
327
+ setTimeout(() => {
328
+ const file = fileInput.files[0];
329
+ const reader = new FileReader();
330
+
331
+ reader.onload = function(e) {
332
+ try {
333
+ const data = new Uint8Array(e.target.result);
334
+ const workbook = XLSX.read(data, { type: 'array' });
335
+
336
+ // Get first worksheet
337
+ const worksheetName = workbook.SheetNames[0];
338
+ const worksheet = workbook.Sheets[worksheetName];
339
+
340
+ // Convert to JSON
341
+ const jsonData = XLSX.utils.sheet_to_json(worksheet);
342
+
343
+ if (jsonData.length === 0) {
344
+ statusElement.textContent = 'Tidak ada data ditemukan dalam file Excel.';
345
+ statusElement.className = 'text-yellow-500';
346
+ resetImportButton();
347
+ return;
348
+ }
349
+
350
+ // Process the data and add to localStorage
351
+ const jobs = getJobs();
352
+ let importedCount = 0;
353
+
354
+ // Define expected column mapping
355
+ const columnMapping = {
356
+ 'tanggal': ['tanggal', 'date', 'Tanggal', 'Date', 'TGL', 'tgl', 'created', 'Created'],
357
+ 'kegiatan': ['kegiatan', 'task', 'Kegiatan', 'Task', 'Tugas', 'tugas', 'Description', 'description', 'Content', 'content'],
358
+ 'status': ['status', 'Status', 'STATUS', 'Completion', 'completion'],
359
+ 'kategori': ['kategori', 'category', 'Kategori', 'Category', 'Jenis', 'jenis', 'Type', 'type', 'Class', 'class']
360
+ };
361
+
362
+ // Auto-detect columns
363
+ const headerRow = XLSX.utils.sheet_to_json(worksheet, { header: 1 })[0];
364
+ const columns = {};
365
+
366
+ if (headerRow) {
367
+ headerRow.forEach((header, index) => {
368
+ const headerStr = header.toString().trim();
369
+ for (const [standardName, possibleNames] of Object.entries(columnMapping)) {
370
+ if (possibleNames.includes(headerStr)) {
371
+ columns[standardName] = index;
372
+ break;
373
+ }
374
+ }
375
+ });
376
+ }
377
+
378
+ // If we couldn't detect columns from headers, assume standard order
379
+ if (Object.keys(columns).length < 2) {
380
+ columns.tanggal = 0;
381
+ columns.kegiatan = 1;
382
+ columns.status = 2;
383
+ columns.kategori = 3;
384
+ }
385
+
386
+ // Show status update for processing
387
+ statusElement.textContent = `Memproses ${jsonData.length} baris data...`;
388
+
389
+ // Process each row
390
+ const processNextRow = (index) => {
391
+ if (index >= jsonData.length) {
392
+ // All done - save and finish
393
+ saveJobs(jobs);
394
+ filterJobs();
395
+ filterReview();
396
+ filterChart();
397
+
398
+ statusElement.textContent = `Berhasil mengimpor ${importedCount} tugas dari file Excel.`;
399
+ statusElement.className = 'text-green-500';
400
+
401
+ // Reset file input
402
+ fileInput.value = '';
403
+ resetImportButton();
404
+ return;
405
+ }
406
+
407
+ // Process one row at a time with short delay to keep UI responsive
408
+ const row = jsonData[index];
409
+ statusElement.textContent = `Memproses data (${index + 1}/${jsonData.length})...`;
410
+
411
+ // Handle row processing based on whether we have headers
412
+ if (headerRow.length > 0) {
413
+ // Convert worksheet to array to access by index
414
+ const allRows = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
415
+ const rowIndex = index + 1; // +1 for header
416
+ if (rowIndex < allRows.length) {
417
+ const rowArray = allRows[rowIndex];
418
+ const date = formatDateForStorage(rowArray[columns.tanggal]);
419
+ const task = rowArray[columns.kegiatan];
420
+ const status = rowArray[columns.status] || 'Belum Mulai';
421
+ const category = rowArray[columns.kategori] || 'Lainnya';
422
+
423
+ if (date && task) {
424
+ jobs.push({
425
+ id: Date.now() + importedCount,
426
+ date,
427
+ task,
428
+ status,
429
+ category,
430
+ timestamp: new Date().toISOString()
431
+ });
432
+ importedCount++;
433
+ }
434
+ }
435
+ } else {
436
+ // No headers - use objects values
437
+ const values = Object.values(row);
438
+ const date = formatDateForStorage(values[columns.tanggal]);
439
+ const task = values[columns.kegiatan];
440
+ const status = values[columns.status] || 'Belum Mulai';
441
+ const category = values[columns.kategori] || 'Lainnya';
442
+
443
+ if (date && task) {
444
+ jobs.push({
445
+ id: Date.now() + importedCount,
446
+ date,
447
+ task,
448
+ status,
449
+ category,
450
+ timestamp: new Date().toISOString()
451
+ });
452
+ importedCount++;
453
+ }
454
+ }
455
+
456
+ // Process next row after a short delay
457
+ setTimeout(() => processNextRow(index + 1), 10);
458
+ };
459
+
460
+ // Start processing rows
461
+ processNextRow(0);
462
+
463
+ } catch (error) {
464
+ console.error("Error processing Excel file:", error);
465
+ statusElement.textContent = `Gagal membaca file Excel: ${error.message}`;
466
+ statusElement.className = 'text-red-500';
467
+ resetImportButton();
468
+ }
469
+ };
470
+
471
+ reader.onerror = function() {
472
+ statusElement.textContent = 'Gagal membaca file.';
473
+ statusElement.className = 'text-red-500';
474
+ resetImportButton();
475
+ };
476
+
477
+ reader.readAsArrayBuffer(file);
478
+ }, 100); // Small delay to allow UI to update
479
+ }
480
+
481
+ // Reset import button to normal state
482
+ function resetImportButton() {
483
+ const importProgressText = document.getElementById('importProgressText');
484
+ const loadingSpinner = document.getElementById('loadingSpinner');
485
+ importProgressText.classList.remove('hidden');
486
+ loadingSpinner.classList.add('hidden');
487
+ }
488
+
489
+ // Format date from Excel to standard format
490
+ function formatDateForStorage(dateValue) {
491
+ // If it's already a string in yyyy-mm-dd format
492
+ if (typeof dateValue === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(dateValue)) {
493
+ return dateValue;
494
+ }
495
+
496
+ // If it's an Excel date serial number (common in .xlsx files)
497
+ if (typeof dateValue === 'number' && dateValue > 1) {
498
+ // Excel's base date is January 1, 1900, but there's a known bug where it treats 1900 as a leap year
499
+ // We need to adjust for this when converting
500
+ const is1900LeapBug = dateValue >= 60; // Dates after "February 29, 1900"
501
+ const correctedDateValue = dateValue - (is1900LeapBug ? 2 : 1); // Subtract 2 for the leap year bug and 1 for the day offset
502
+ const date = new Date(Date.UTC(1899, 11, 31 + correctedDateValue)); // Base date is December 31, 1899
503
+ return date.toISOString().split('T')[0];
504
+ }
505
+
506
+ // If it's a date object
507
+ if (dateValue instanceof Date) {
508
+ return dateValue.toISOString().split('T')[0];
509
+ }
510
+
511
+ // Try to parse as string
512
+ if (typeof dateValue === 'string') {
513
+ // Handle Indonesian date formats like "01/Jan/2023"
514
+ const idDateRegex = /^(\d{1,2})\/([A-Za-z]{3})\/(\d{4})$/;
515
+ if (idDateRegex.test(dateValue)) {
516
+ const months = {
517
+ 'Jan': 0, 'Feb': 1, 'Mar': 2, 'Apr': 3, 'Mei': 4, 'Jun': 5,
518
+ 'Jul': 6, 'Agu': 7, 'Sep': 8, 'Okt': 9, 'Nov': 10, 'Des': 11
519
+ };
520
+
521
+ const match = dateValue.match(idDateRegex);
522
+ const day = parseInt(match[1]);
523
+ const month = months[match[2]];
524
+ const year = parseInt(match[3]);
525
+
526
+ if (month !== undefined) {
527
+ const date = new Date(year, month, day);
528
+ if (!isNaN(date.getTime())) {
529
+ return date.toISOString().split('T')[0];
530
+ }
531
+ }
532
+ }
533
+
534
+ // Handle other common date formats
535
+ const date = new Date(dateValue);
536
+ if (!isNaN(date.getTime())) {
537
+ return date.toISOString().split('T')[0];
538
+ }
539
+ }
540
+
541
+ return null;
542
+ }
543
+
544
+ // Filter Jobs (with search, date, and pagination)
545
+ function filterJobs() {
546
+ const searchQuery = document.getElementById('searchInput').value.toLowerCase();
547
+ const filterDate = document.getElementById('filterDate').value;
548
+ const jobs = getJobs();
549
+
550
+ // Filter by search and date
551
+ const filtered = jobs.filter(job => {
552
+ const matchesSearch = job.task.toLowerCase().includes(searchQuery);
553
+ const matchesDate = filterDate ? job.date === filterDate : true;
554
+ return matchesSearch && matchesDate;
555
+ });
556
+
557
+ // Sort by date (newest first)
558
+ filtered.sort((a, b) => new Date(b.date) - new Date(a.date));
559
+
560
+ // Pagination
561
+ const totalPages = Math.ceil(filtered.length / ITEMS_PER_PAGE);
562
+ currentPage = Math.min(currentPage, totalPages || 1);
563
+
564
+ const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
565
+ const paginatedJobs = filtered.slice(startIndex, startIndex + ITEMS_PER_PAGE);
566
+
567
+ const container = document.getElementById('historyList');
568
+ container.innerHTML = '';
569
+
570
+ if (filtered.length === 0) {
571
+ container.innerHTML = '<p class="text-gray-500">Tidak ada tugas ditemukan berdasarkan filter yang diterapkan.</p>';
572
+ document.getElementById('paginationContainer').innerHTML = '';
573
+ return;
574
+ }
575
+
576
+ // Display paginated jobs
577
+ paginatedJobs.forEach(job => {
578
+ const el = document.createElement('div');
579
+ el.className = 'p-4 border border-gray-200 rounded bg-gray-50';
580
+ el.innerHTML = `
581
+ <div class="flex justify-between">
582
+ <strong>${formatDate(job.date)} - ${job.category}</strong>
583
+ <span class="px-2 py-1 text-xs rounded-full ${
584
+ job.status === 'Selesai' ? 'bg-green-200 text-green-800' :
585
+ job.status === 'Dalam Proses' ? 'bg-yellow-200 text-yellow-800' :
586
+ 'bg-gray-200 text-gray-800'
587
+ }">${job.status}</span>
588
+ </div>
589
+ <p class="mt-2">${job.task}</p>
590
+ ${job.attachments && job.attachments.length > 0 ?
591
+ `<div class="mt-2">
592
+ <strong class="text-sm">Lampiran:</strong>
593
+ <div class="space-y-1 mt-1">
594
+ ${job.attachments.map(att => `
595
+ <div class="attachment-item">
596
+ <span>📎 ${att.name}</span>
597
+ <a href="${att.url}" target="_blank" download>Unduh</a>
598
+ <button onclick="removeAttachment(${job.id}, '${att.name}')">❌</button>
599
+ </div>
600
+ `).join('')}
601
+ </div>
602
+ </div>` : ''}
603
+ <button onclick="deleteJob(${job.id})" class="text-red-500 text-sm mt-2 hover:underline">❌ Hapus</button>
604
+ `;
605
+ container.appendChild(el);
606
+ });
607
+
608
+ // Generate pagination
609
+ generatePagination(totalPages, filtered.length);
610
+ }
611
+
612
+ // Generate pagination controls
613
+ function generatePagination(totalPages, totalItems) {
614
+ const paginationContainer = document.getElementById('paginationContainer');
615
+
616
+ if (totalPages <= 1) {
617
+ paginationContainer.innerHTML = '';
618
+ return;
619
+ }
620
+
621
+ let paginationHTML = '<div class="text-sm text-gray-600 mr-4">Total: ' + totalItems + ' tugas</div>';
622
+
623
+ // Previous button
624
+ paginationHTML += `<button onclick="changePage(${currentPage - 1})" ${currentPage === 1 ? 'disabled' : ''} class="px-2 py-1">&lt;</button>`;
625
+
626
+ // Page numbers
627
+ const startPage = Math.max(1, currentPage - 2);
628
+ const endPage = Math.min(totalPages, startPage + 4);
629
+
630
+ for (let i = startPage; i <= endPage; i++) {
631
+ paginationHTML += `<button onclick="changePage(${i})" class="${i === currentPage ? 'bg-indigo-600 text-white' : 'bg-white'}">${i}</button>`;
632
+ }
633
+
634
+ // Next button
635
+ paginationHTML += `<button onclick="changePage(${currentPage + 1})" ${currentPage === totalPages ? 'disabled' : ''} class="px-2 py-1">&gt;</button>`;
636
+
637
+ paginationContainer.innerHTML = paginationHTML;
638
+ }
639
+
640
+ // Change page
641
+ function changePage(page) {
642
+ currentPage = page;
643
+ filterJobs();
644
+
645
+ // Scroll to top of list
646
+ document.getElementById('historyList').scrollIntoView({ behavior: 'smooth' });
647
+ }
648
+
649
+ // Export to Excel from History
650
+ function exportToExcel() {
651
+ const searchQuery = document.getElementById('searchInput').value.toLowerCase();
652
+ const filterDate = document.getElementById('filterDate').value;
653
+ const jobs = getJobs();
654
+
655
+ // Filter by search and date
656
+ let filtered = jobs.filter(job => {
657
+ const matchesSearch = job.task.toLowerCase().includes(searchQuery);
658
+ const matchesDate = filterDate ? job.date === filterDate : true;
659
+ return matchesSearch && matchesDate;
660
+ });
661
+
662
+ // Sort by date (newest first)
663
+ filtered.sort((a, b) => new Date(b.date) - new Date(a.date));
664
+
665
+ // If no data, show alert
666
+ if (filtered.length === 0) {
667
+ alert('Tidak ada data untuk diekspor.');
668
+ return;
669
+ }
670
+
671
+ // Prepare data for export
672
+ const exportData = filtered.map(job => ({
673
+ 'Tanggal': formatDateCSV(job.date),
674
+ 'Kegiatan': job.task,
675
+ 'Status': job.status,
676
+ 'Kategori': job.category,
677
+ 'Tanggal Input': new Date(job.timestamp).toLocaleString('id-ID')
678
+ }));
679
+
680
+ // Create worksheet
681
+ const worksheet = XLSX.utils.json_to_sheet(exportData);
682
+
683
+ // Set column widths
684
+ const wscols = [
685
+ {wch: 12}, // Tanggal
686
+ {wch: 40}, // Kegiatan
687
+ {wch: 15}, // Status
688
+ {wch: 15}, // Kategori
689
+ {wch: 20} // Tanggal Input
690
+ ];
691
+ worksheet['!cols'] = wscols;
692
+
693
+ // Create workbook
694
+ const workbook = XLSX.utils.book_new();
695
+ XLSX.utils.book_append_sheet(workbook, worksheet, 'Riwayat Tugas');
696
+
697
+ // Generate filename with current date
698
+ const dateStr = new Date().toISOString().slice(0, 10);
699
+ const filename = `Riwayat_Tugas_${dateStr}.xlsx`;
700
+
701
+ // Export
702
+ XLSX.writeFile(workbook, filename);
703
+ }
704
+
705
+ // Export to Excel from Review
706
+ function exportReviewToExcel() {
707
+ const statusFilter = document.getElementById('statusFilter').value;
708
+ const startDate = document.getElementById('reviewStartDate').value;
709
+ const endDate = document.getElementById('reviewEndDate').value;
710
+ const jobs = getJobs();
711
+
712
+ // Filter by status and date range
713
+ let filtered = jobs.filter(job => {
714
+ const matchesStatus = statusFilter ? job.status === statusFilter : true;
715
+ const jobDate = new Date(job.date);
716
+ const afterStart = startDate ? jobDate >= new Date(startDate) : true;
717
+ const beforeEnd = endDate ? jobDate <= new Date(endDate) : true;
718
+ return matchesStatus && afterStart && beforeEnd;
719
+ });
720
+
721
+ // Sort by date (newest first)
722
+ filtered.sort((a, b) => new Date(b.date) - new Date(a.date));
723
+
724
+ // If no data, show alert
725
+ if (filtered.length === 0) {
726
+ alert('Tidak ada data untuk diekspor.');
727
+ return;
728
+ }
729
+
730
+ // Prepare data for export
731
+ const exportData = filtered.map(job => ({
732
+ 'Tanggal': formatDateCSV(job.date),
733
+ 'Kegiatan': job.task,
734
+ 'Status': job.status,
735
+ 'Kategori': job.category,
736
+ 'Tanggal Input': new Date(job.timestamp).toLocaleString('id-ID')
737
+ }));
738
+
739
+ // Create worksheet
740
+ const worksheet = XLSX.utils.json_to_sheet(exportData);
741
+
742
+ // Set column widths
743
+ const wscols = [
744
+ {wch: 12}, // Tanggal
745
+ {wch: 40}, // Kegiatan
746
+ {wch: 15}, // Status
747
+ {wch: 15}, // Kategori
748
+ {wch: 20} // Tanggal Input
749
+ ];
750
+ worksheet['!cols'] = wscols;
751
+
752
+ // Create workbook
753
+ const workbook = XLSX.utils.book_new();
754
+ XLSX.utils.book_append_sheet(workbook, worksheet, 'Review Tugas');
755
+
756
+ // Generate filename with current date and filters
757
+ const dateStr = new Date().toISOString().slice(0, 10);
758
+ let filename = `Review_Tugas_${dateStr}`;
759
+ if (statusFilter) filename += `_${statusFilter}`;
760
+ filename += '.xlsx';
761
+
762
+ // Export
763
+ XLSX.writeFile(workbook, filename);
764
+ }
765
+
766
+ // Format date for CSV
767
+ function formatDateCSV(dateStr) {
768
+ return new Date(dateStr).toLocaleDateString('id-ID');
769
+ }
770
+
771
+ // Remove attachment
772
+ function removeAttachment(jobId, attachmentName) {
773
+ if (confirm(`Hapus lampiran ${attachmentName}?`)) {
774
+ let jobs = getJobs();
775
+ const jobIndex = jobs.findIndex(j => j.id === jobId);
776
+
777
+ if (jobIndex !== -1) {
778
+ jobs[jobIndex].attachments = jobs[jobIndex].attachments.filter(
779
+ att => att.name !== attachmentName
780
+ );
781
+
782
+ saveJobs(jobs);
783
+ filterJobs();
784
+ }
785
+ }
786
+ }
787
+
788
+ // Delete Job
789
+ function deleteJob(id) {
790
+ if (confirm('Anda yakin ingin menghapus tugas ini?')) {
791
+ let jobs = getJobs();
792
+ jobs = jobs.filter(job => job.id !== id);
793
+ saveJobs(jobs);
794
+ filterJobs();
795
+ filterReview();
796
+ filterChart();
797
+ alert('Tugas dihapus.');
798
+ }
799
+ }
800
+
801
+ // Filter Review
802
+ function filterReview() {
803
+ const statusFilter = document.getElementById('statusFilter').value;
804
+ const startDate = document.getElementById('reviewStartDate').value;
805
+ const endDate = document.getElementById('reviewEndDate').value;
806
+ const jobs = getJobs();
807
+
808
+ // Filter by status and date range
809
+ const filtered = jobs.filter(job => {
810
+ const matchesStatus = statusFilter ? job.status === statusFilter : true;
811
+ const jobDate = new Date(job.date);
812
+ const afterStart = startDate ? jobDate >= new Date(startDate) : true;
813
+ const beforeEnd = endDate ? jobDate <= new Date(endDate) : true;
814
+ return matchesStatus && afterStart && beforeEnd;
815
+ });
816
+
817
+ // Sort by date (newest first)
818
+ filtered.sort((a, b) => new Date(b.date) - new Date(a.date));
819
+
820
+ const container = document.getElementById('reviewList');
821
+ container.innerHTML = '';
822
+
823
+ // Show active filters
824
+ const activeFiltersContainer = document.getElementById('activeFilters');
825
+ activeFiltersContainer.innerHTML = '';
826
+ if (statusFilter) {
827
+ createFilterBadge(activeFiltersContainer, `Status: ${statusFilter}`);
828
+ }
829
+ if (startDate) {
830
+ createFilterBadge(activeFiltersContainer, `Dari: ${formatDate(startDate)}`);
831
+ }
832
+ if (endDate) {
833
+ createFilterBadge(activeFiltersContainer, `Hingga: ${formatDate(endDate)}`);
834
+ }
835
+
836
+ if (filtered.length === 0) {
837
+ container.innerHTML = '<p class="text-gray-500">Tidak ada tugas sesuai dengan filter yang diterapkan.</p>';
838
+ return;
839
+ }
840
+
841
+ filtered.forEach(job => {
842
+ const el = document.createElement('div');
843
+ el.className = 'p-4 border border-gray-200 rounded bg-white';
844
+ el.innerHTML = `
845
+ <div class="flex justify-between items-start">
846
+ <div>
847
+ <strong>${formatDate(job.date)} - ${job.category}</strong>
848
+ <p class="mt-1 text-gray-700">${job.task}</p>
849
+ ${job.attachments && job.attachments.length > 0 ?
850
+ `<div class="mt-1">
851
+ <span class="text-xs">📎 ${job.attachments.length} lampiran</span>
852
+ </div>` : ''}
853
+ </div>
854
+ <span class="px-2 py-1 text-xs rounded-full ${
855
+ job.status === 'Selesai' ? 'bg-green-200 text-green-800' :
856
+ job.status === 'Dalam Proses' ? 'bg-yellow-200 text-yellow-800' :
857
+ 'bg-gray-200 text-gray-800'
858
+ }">${job.status}</span>
859
+ </div>
860
+ `;
861
+ container.appendChild(el);
862
+ });
863
+ }
864
+
865
+ // Create filter badge
866
+ function createFilterBadge(container, text) {
867
+ const badge = document.createElement('span');
868
+ badge.className = `filter-badge ${
869
+ text.startsWith('Status') ? 'bg-indigo-100 text-indigo-800' :
870
+ text.startsWith('Dari') ? 'bg-blue-100 text-blue-800' :
871
+ 'bg-purple-100 text-purple-800'
872
+ }`;
873
+ badge.textContent = text;
874
+ container.appendChild(badge);
875
+ }
876
+
877
+ // Filter Chart
878
+ function filterChart() {
879
+ const startDate = document.getElementById('chartStartDate').value;
880
+ const endDate = document.getElementById('chartEndDate').value;
881
+ const jobs = getJobs();
882
+
883
+ // Filter jobs within date range
884
+ const filteredJobs = jobs.filter(job => {
885
+ const jobDate = new Date(job.date);
886
+ const afterStart = startDate ? jobDate >= new Date(startDate) : true;
887
+ const beforeEnd = endDate ? jobDate <= new Date(endDate) : true;
888
+ return afterStart && beforeEnd;
889
+ });
890
+
891
+ // Group by date
892
+ const dateMap = new Map();
893
+
894
+ // If no start/end date, show last 7 days by default
895
+ if (!startDate && !endDate) {
896
+ const end = new Date();
897
+ const start = new Date();
898
+ start.setDate(end.getDate() - 6);
899
+
900
+ let current = new Date(start);
901
+ while (current <= end) {
902
+ const dateStr = current.toISOString().split('T')[0];
903
+ dateMap.set(dateStr, 0);
904
+ current.setDate(current.getDate() + 1);
905
+ }
906
+ } else if (startDate && endDate) {
907
+ // Initialize all dates in range
908
+ const start = new Date(startDate);
909
+ const end = new Date(endDate);
910
+ let current = new Date(start);
911
+ while (current <= end) {
912
+ const dateStr = current.toISOString().split('T')[0];
913
+ dateMap.set(dateStr, 0);
914
+ current.setDate(current.getDate() + 1);
915
+ }
916
+ }
917
+
918
+ // Count completed jobs
919
+ filteredJobs.forEach(job => {
920
+ if (job.status === 'Selesai') {
921
+ const dateStr = job.date;
922
+ dateMap.set(dateStr, (dateMap.get(dateStr) || 0) + 1);
923
+ }
924
+ });
925
+
926
+ // Sort dates
927
+ const sortedDates = Array.from(dateMap.keys()).sort();
928
+ const labels = sortedDates.map(formatDate);
929
+ const data = sortedDates.map(date => dateMap.get(date));
930
+
931
+ // Destroy previous chart
932
+ if (window.dailyChart) {
933
+ window.dailyChart.destroy();
934
+ }
935
+
936
+ const ctx = document.getElementById('productivityChart').getContext('2d');
937
+ window.dailyChart = new Chart(ctx, {
938
+ type: 'bar',
939
+ data: {
940
+ labels: labels,
941
+ datasets: [{
942
+ label: 'Jumlah Tugas Selesai',
943
+ data: data,
944
+ backgroundColor: 'rgba(79, 70, 229, 0.7)',
945
+ borderColor: 'rgba(79, 70, 229, 1)',
946
+ borderWidth: 1
947
+ }]
948
+ },
949
+ options: {
950
+ responsive: true,
951
+ plugins: {
952
+ tooltip: {
953
+ mode: 'index',
954
+ intersect: false,
955
+ },
956
+ legend: {
957
+ display: true,
958
+ position: 'top'
959
+ }
960
+ },
961
+ scales: {
962
+ y: {
963
+ beginAtZero: true,
964
+ ticks: {
965
+ stepSize: 1
966
+ },
967
+ title: {
968
+ display: true,
969
+ text: 'Jumlah Tugas Selesai'
970
+ }
971
+ },
972
+ x: {
973
+ title: {
974
+ display: true,
975
+ text: 'Tanggal'
976
+ }
977
+ }
978
+ }
979
+ }
980
+ });
981
+ }
982
+
983
+ // Helper: Format date
984
+ function formatDate(dateStr) {
985
+ const options = { year: 'numeric', month: 'short', day: 'numeric' };
986
+ return new Date(dateStr).toLocaleDateString('id-ID', options);
987
+ }
988
+
989
+ // Auto-set today's date and initialize
990
+ document.addEventListener('DOMContentLoaded', () => {
991
+ const today = new Date();
992
+ const formattedToday = today.toISOString().split('T')[0];
993
+
994
+ document.getElementById('jobDate').value = formattedToday;
995
+ document.getElementById('filterDate').value = formattedToday;
996
+ document.getElementById('reviewStartDate').value = formattedToday;
997
+ document.getElementById('reviewEndDate').value = formattedToday;
998
+ document.getElementById('chartStartDate').value = formattedToday;
999
+ document.getElementById('chartEndDate').value = formattedToday;
1000
+
1001
+ filterJobs();
1002
+ filterReview();
1003
+ filterChart();
1004
+ });
1005
+
1006
+ // Initial display
1007
+ showTab('input');
1008
+ </script>
1009
+ <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-qwensite.hf.space/logo.svg" alt="qwensite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-qwensite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >QwenSite</a> - 🧬 <a href="https://enzostvs-qwensite.hf.space?remix=alterzick/https-huggingface-co-spaces-alterzick-daily-job-tracker-v1" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
1010
+ </html>
prompts.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ untuk riwayat pekerjaan tolong buatkan filter tanggal agar dapat pekerjaan saat tanggal tertentu
2
+ untuk review , tolong buat filter berdasarkan status pekerjaan , dan tambahkan untuk grafik buat filter bedasarkan dari tanggal berapa sampai tanggal berapa
3
+ untuk fitur import dalam bentuk excel data tolong tambahkan , dan untuk fitur input attachment tambahkan ( pastikan disimpan dalam folder data)
4
+ untuk riwayat tambahkan fitur export excel , dan untuk input job import data tidak terbatas dengan excel
5
+ tambahkan export ke excel di review