Quickly fill volunteer time log forms with saved information
Size
32.8 KB
Version
1.2.4
Created
Mar 6, 2026
Updated
15 days ago
1// ==UserScript==
2// @name Volunteer Time Log Auto-Filler
3// @description Quickly fill volunteer time log forms with saved information
4// @version 1.2.4
5// @match https://*.forms.office.com/*
6// @icon https://cdn.forms.office.net/images/favicon.ico
7// ==/UserScript==
8(function() {
9 'use strict';
10
11 console.log('Volunteer Time Log Auto-Filler loaded');
12
13 // Helper function to parse time string like "10am - 3pm"
14 function parseTimeRange(timeString) {
15 const parts = timeString.toLowerCase().split('-').map(s => s.trim());
16 if (parts.length !== 2) {
17 throw new Error('Invalid time format. Use format like "10am - 3pm"');
18 }
19
20 const parseTime = (timeStr) => {
21 // Match formats like: 10am, 10:30am, 3.30pm, 3:30pm
22 const match = timeStr.match(/(\d+)[:.]?(\d+)?\s*(am|pm)/i);
23 if (!match) {
24 throw new Error('Invalid time format. Use format like "10am" or "10:30pm"');
25 }
26 let hour = parseInt(match[1]);
27 const minute = match[2] ? parseInt(match[2]) : 0;
28 const ampm = match[3].toLowerCase();
29
30 console.log(`Parsed time "${timeStr}": hour=${hour}, minute=${minute}, ampm=${ampm}`);
31 return { hour, minute, ampm };
32 };
33
34 return {
35 start: parseTime(parts[0]),
36 end: parseTime(parts[1])
37 };
38 }
39
40 // Helper function to parse date string
41 function parseDate(dateString) {
42 if (!dateString || dateString.trim() === '') {
43 // Return today's date if no date specified
44 const today = new Date();
45 return `${today.getMonth() + 1}/${today.getDate()}/${today.getFullYear()}`;
46 }
47
48 // Try to parse dd/mm/yy or dd/mm/yyyy format
49 const parts = dateString.trim().split('/');
50 if (parts.length === 3) {
51 const day = parseInt(parts[0]);
52 const month = parseInt(parts[1]);
53 let year = parseInt(parts[2]);
54
55 // Handle 2-digit year (yy format)
56 if (year < 100) {
57 // Assume 20xx for years 00-99
58 year = 2000 + year;
59 }
60
61 // Validate the date
62 if (day >= 1 && day <= 31 && month >= 1 && month <= 12 && year >= 2000) {
63 // Return in M/d/yyyy format for the form
64 return `${month}/${day}/${year}`;
65 }
66 }
67
68 throw new Error('Invalid date format. Use format like "25/12/24" or "25/12/2024"');
69 }
70
71 // Helper function to click dropdown and select option
72 async function selectDropdownOption(questionId, value) {
73 console.log(`Selecting dropdown for ${questionId} with value: ${value}`);
74
75 // Find the question container
76 const questionContainer = document.querySelector(`#${questionId}`).closest('[data-automation-id="questionItem"]');
77
78 // Click the dropdown button to open it
79 const dropdownButton = questionContainer.querySelector('[role="button"]');
80 if (!dropdownButton) {
81 console.error('Dropdown button not found');
82 return false;
83 }
84
85 dropdownButton.click();
86
87 // Wait for dropdown to open
88 await new Promise(resolve => setTimeout(resolve, 300));
89
90 // Find and click the option
91 const options = document.querySelectorAll('[role="option"]');
92 for (const option of options) {
93 const optionText = option.textContent.trim();
94 // Try exact match first
95 if (optionText === value.toString()) {
96 option.click();
97 await new Promise(resolve => setTimeout(resolve, 200));
98 console.log(`Selected option: ${optionText}`);
99 return true;
100 }
101 // Try numeric match (e.g., "3" matches "03" or "3")
102 if (!isNaN(value) && !isNaN(optionText) && parseInt(optionText) === parseInt(value)) {
103 option.click();
104 await new Promise(resolve => setTimeout(resolve, 200));
105 console.log(`Selected option: ${optionText}`);
106 return true;
107 }
108 }
109
110 console.error(`Option "${value}" not found. Available options:`, Array.from(options).map(o => o.textContent.trim()));
111 return false;
112 }
113
114 // Helper function to select checkbox option
115 async function selectCheckboxOption(questionId, value) {
116 console.log(`Selecting checkbox for ${questionId} with value: ${value}`);
117
118 // Find the question container
119 const questionContainer = document.querySelector(`#${questionId}`).closest('[data-automation-id="questionItem"]');
120
121 // Find all checkbox items
122 const checkboxItems = questionContainer.querySelectorAll('[data-automation-id="choiceItem"]');
123
124 for (const item of checkboxItems) {
125 const label = item.querySelector('label');
126 if (label && label.textContent.trim() === value) {
127 // Try clicking the label instead of the checkbox span
128 label.click();
129 await new Promise(resolve => setTimeout(resolve, 300));
130 console.log(`Checkbox "${value}" selected`);
131 return true;
132 }
133 }
134
135 console.error(`Checkbox option "${value}" not found`);
136 return false;
137 }
138
139 // Helper function to select radio button option
140 async function selectRadioOption(questionId, value) {
141 console.log(`Selecting radio for ${questionId} with value: ${value}`);
142
143 // Find the question container
144 const questionContainer = document.querySelector(`#${questionId}`).closest('[data-automation-id="questionItem"]');
145
146 // Find all radio items
147 const radioItems = questionContainer.querySelectorAll('[data-automation-id="choiceItem"]');
148
149 for (const item of radioItems) {
150 const label = item.querySelector('label');
151 if (label && label.textContent.includes(value)) {
152 const radio = item.querySelector('[data-automation-id="radio"]');
153 if (radio) {
154 radio.click();
155 await new Promise(resolve => setTimeout(resolve, 200));
156 console.log(`Radio "${value}" selected`);
157 return true;
158 }
159 }
160 }
161
162 console.error(`Radio option "${value}" not found`);
163 return false;
164 }
165
166 // History management functions
167 async function saveSubmissionToHistory(name, email, duration, date) {
168 try {
169 const history = await getHistory();
170 const submission = {
171 name: name,
172 email: email,
173 duration: duration,
174 date: date,
175 timestamp: new Date().toISOString(),
176 submittedAt: new Date().toLocaleString()
177 };
178
179 history.push(submission);
180 await GM.setValue('volunteer_history', JSON.stringify(history));
181 console.log('Submission saved to history:', submission);
182 return true;
183 } catch (error) {
184 console.error('Error saving to history:', error);
185 return false;
186 }
187 }
188
189 async function getHistory() {
190 try {
191 const historyJson = await GM.getValue('volunteer_history', '[]');
192 return JSON.parse(historyJson);
193 } catch (error) {
194 console.error('Error loading history:', error);
195 return [];
196 }
197 }
198
199 async function clearHistory() {
200 try {
201 await GM.setValue('volunteer_history', '[]');
202 console.log('History cleared');
203 return true;
204 } catch (error) {
205 console.error('Error clearing history:', error);
206 return false;
207 }
208 }
209
210 async function deleteHistoryEntry(timestamp) {
211 try {
212 const history = await getHistory();
213 const filteredHistory = history.filter(entry => entry.timestamp !== timestamp);
214 await GM.setValue('volunteer_history', JSON.stringify(filteredHistory));
215 console.log('History entry deleted:', timestamp);
216 return true;
217 } catch (error) {
218 console.error('Error deleting history entry:', error);
219 return false;
220 }
221 }
222
223 // Helper function to format date from M/d/yyyy to dd/mm/yyyy
224 function formatDateToDDMMYYYY(dateString) {
225 try {
226 // Parse M/d/yyyy format
227 const parts = dateString.split('/');
228 if (parts.length === 3) {
229 const month = parts[0].padStart(2, '0');
230 const day = parts[1].padStart(2, '0');
231 const year = parts[2];
232 return `${day}/${month}/${year}`;
233 }
234 return dateString;
235 } catch (error) {
236 return dateString;
237 }
238 }
239
240 // Helper function to format submitted date to dd/mm/yyyy format
241 function formatSubmittedDate(dateString) {
242 try {
243 // Parse the locale date string and convert to dd/mm/yyyy
244 const date = new Date(dateString);
245 if (!isNaN(date.getTime())) {
246 const day = date.getDate().toString().padStart(2, '0');
247 const month = (date.getMonth() + 1).toString().padStart(2, '0');
248 const year = date.getFullYear();
249 const hours = date.getHours().toString().padStart(2, '0');
250 const minutes = date.getMinutes().toString().padStart(2, '0');
251 const seconds = date.getSeconds().toString().padStart(2, '0');
252 return `${day}/${month}/${year}, ${hours}:${minutes}:${seconds}`;
253 }
254 return dateString;
255 } catch (error) {
256 return dateString;
257 }
258 }
259
260 async function exportHistory() {
261 try {
262 const history = await getHistory();
263 const dataStr = JSON.stringify(history, null, 2);
264 const dataBlob = new Blob([dataStr], { type: 'application/json' });
265 const url = URL.createObjectURL(dataBlob);
266
267 const link = document.createElement('a');
268 link.href = url;
269 link.download = `volunteer_history_${new Date().toISOString().split('T')[0]}.json`;
270 document.body.appendChild(link);
271 link.click();
272 document.body.removeChild(link);
273 URL.revokeObjectURL(url);
274
275 console.log('History exported successfully');
276 return true;
277 } catch (error) {
278 console.error('Error exporting history:', error);
279 return false;
280 }
281 }
282
283 async function importHistory(fileContent) {
284 try {
285 const importedData = JSON.parse(fileContent);
286 if (!Array.isArray(importedData)) {
287 throw new Error('Invalid history file format');
288 }
289
290 const currentHistory = await getHistory();
291 const mergedHistory = [...currentHistory, ...importedData];
292
293 // Remove duplicates based on timestamp
294 const uniqueHistory = mergedHistory.filter((item, index, self) =>
295 index === self.findIndex((t) => t.timestamp === item.timestamp)
296 );
297
298 await GM.setValue('volunteer_history', JSON.stringify(uniqueHistory));
299 console.log('History imported and merged successfully');
300 return true;
301 } catch (error) {
302 console.error('Error importing history:', error);
303 throw error;
304 }
305 }
306
307 function groupHistoryByNameAndDate(history) {
308 const grouped = {};
309
310 history.forEach(entry => {
311 if (!grouped[entry.name]) {
312 grouped[entry.name] = {};
313 }
314 if (!grouped[entry.name][entry.date]) {
315 grouped[entry.name][entry.date] = [];
316 }
317 grouped[entry.name][entry.date].push(entry);
318 });
319
320 return grouped;
321 }
322
323 // Main fill function
324 async function fillForm(name, email, duration, date) {
325 try {
326 console.log('Starting form fill...', { name, email, duration, date });
327
328 // Parse the time range
329 const times = parseTimeRange(duration);
330 console.log('Parsed times:', times);
331
332 // Parse the date
333 const dateString = parseDate(date);
334 console.log('Parsed date:', dateString);
335
336 // Fill Name (Question 1)
337 const nameInput = document.querySelector('#QuestionId_r77aee835933d418eb77c8e58d3a829d1').closest('[data-automation-id="questionItem"]').querySelector('input[data-automation-id="textInput"]');
338 if (nameInput) {
339 nameInput.value = name;
340 nameInput.dispatchEvent(new Event('input', { bubbles: true }));
341 console.log('Name filled');
342 }
343
344 // Fill Email (Question 2)
345 const emailInput = document.querySelector('#QuestionId_r4848a2931f5d4bc0bd59975b96f7eb72').closest('[data-automation-id="questionItem"]').querySelector('input[data-automation-id="textInput"]');
346 if (emailInput) {
347 emailInput.value = email;
348 emailInput.dispatchEvent(new Event('input', { bubbles: true }));
349 console.log('Email filled');
350 }
351
352 // Fill Date (Question 3)
353 const dateInput = document.querySelector('#DatePicker0-label');
354 if (dateInput) {
355 dateInput.value = dateString;
356 dateInput.dispatchEvent(new Event('input', { bubbles: true }));
357 dateInput.dispatchEvent(new Event('change', { bubbles: true }));
358 console.log('Date filled:', dateString);
359 }
360
361 await new Promise(resolve => setTimeout(resolve, 500));
362
363 // Fill Start Time Hour (Question 4)
364 await selectDropdownOption('QuestionId_r9b6cf34c00ab4e529f7e369ee8195e2b', times.start.hour);
365
366 // Fill Start Time Minute (Question 5)
367 await selectDropdownOption('QuestionId_rd5ab020ea2f649f88b10f0d255085a79', times.start.minute.toString().padStart(2, '0'));
368
369 // Fill Start Time AM/PM (Question 6)
370 await selectDropdownOption('QuestionId_r61065e1c0eef4882a2c871e4effbb4f7', times.start.ampm.toUpperCase());
371
372 // Fill End Time Hour (Question 7)
373 await selectDropdownOption('QuestionId_r32af9366f9d146ed918e5d1e83f406c4', times.end.hour);
374
375 // Fill End Time Minute (Question 8)
376 await selectDropdownOption('QuestionId_rde751812596e477d8b5d7211188e8855', times.end.minute.toString().padStart(2, '0'));
377
378 // Fill End Time AM/PM (Question 9)
379 await selectDropdownOption('QuestionId_r1a19b78c3c6945dea4f0e4bfc7c57036', times.end.ampm.toUpperCase());
380
381 // Fill Activity (Question 10) - "Routine care support"
382 await new Promise(resolve => setTimeout(resolve, 500));
383 await selectCheckboxOption('QuestionId_rfd71f44e7bdd415b93d23ab35c610db6', 'Routine care support');
384
385 // Skip Question 11 (additional information)
386
387 // Check the consent radio button (Question 12)
388 await new Promise(resolve => setTimeout(resolve, 500));
389 await selectRadioOption('QuestionId_r3d51ddc7fb884938b2b49391386e30df', 'Yes');
390
391 console.log('Form filled successfully!');
392
393 // Save to history
394 await saveSubmissionToHistory(name, email, duration, dateString);
395
396 return true;
397
398 } catch (error) {
399 console.error('Error filling form:', error);
400 alert('Error: ' + error.message);
401 return false;
402 }
403 }
404
405 // Clear form function
406 async function clearForm() {
407 try {
408 console.log('Clearing form...');
409
410 // Clear Name (Question 1)
411 const nameInput = document.querySelector('#QuestionId_r77aee835933d418eb77c8e58d3a829d1').closest('[data-automation-id="questionItem"]').querySelector('input[data-automation-id="textInput"]');
412 if (nameInput) {
413 nameInput.value = '';
414 nameInput.dispatchEvent(new Event('input', { bubbles: true }));
415 }
416
417 // Clear Email (Question 2)
418 const emailInput = document.querySelector('#QuestionId_r4848a2931f5d4bc0bd59975b96f7eb72').closest('[data-automation-id="questionItem"]').querySelector('input[data-automation-id="textInput"]');
419 if (emailInput) {
420 emailInput.value = '';
421 emailInput.dispatchEvent(new Event('input', { bubbles: true }));
422 }
423
424 // Clear Date (Question 3)
425 const dateInput = document.querySelector('#DatePicker0-label');
426 if (dateInput) {
427 dateInput.value = '';
428 dateInput.dispatchEvent(new Event('input', { bubbles: true }));
429 dateInput.dispatchEvent(new Event('change', { bubbles: true }));
430 }
431
432 // Clear all dropdowns and checkboxes by reloading the page
433 location.reload();
434
435 console.log('Form cleared!');
436 return true;
437
438 } catch (error) {
439 console.error('Error clearing form:', error);
440 return false;
441 }
442 }
443
444 // Create the UI panel
445 function createPanel() {
446 const panel = document.createElement('div');
447 panel.id = 'volunteer-autofill-panel';
448 panel.innerHTML = `
449 <div style="position: fixed; top: 20px; right: 20px; background: white; border: 2px solid #0078d4; border-radius: 8px; padding: 20px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 10000; width: 320px; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; max-height: 90vh; overflow-y: auto;">
450 <h3 style="margin: 0 0 15px 0; color: #0078d4; font-size: 18px;">Quick Fill Volunteer Log</h3>
451
452 <!-- Tab Navigation -->
453 <div style="display: flex; margin-bottom: 15px; border-bottom: 2px solid #e0e0e0;">
454 <button id="tab-form" class="tab-btn" style="flex: 1; padding: 10px; background: none; border: none; border-bottom: 3px solid #0078d4; color: #0078d4; font-weight: 600; cursor: pointer; font-size: 13px;">
455 Form Fill
456 </button>
457 <button id="tab-history" class="tab-btn" style="flex: 1; padding: 10px; background: none; border: none; border-bottom: 3px solid transparent; color: #666; font-weight: 600; cursor: pointer; font-size: 13px;">
458 History
459 </button>
460 </div>
461
462 <!-- Form Tab Content -->
463 <div id="form-content" style="display: block;">
464 <div style="margin-bottom: 12px;">
465 <label style="display: block; margin-bottom: 5px; font-weight: 600; color: #333; font-size: 13px;">Select Person:</label>
466 <select id="autofill-person" style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; box-sizing: border-box; background: white;">
467 <option value="">-- Select --</option>
468 <option value="adrian">Adrian Chen</option>
469 <option value="charlene">Charlene Eng</option>
470 </select>
471 </div>
472 <div style="margin-bottom: 12px;">
473 <label style="display: block; margin-bottom: 5px; font-weight: 600; color: #333; font-size: 13px;">Name:</label>
474 <input type="text" id="autofill-name" placeholder="Enter your name" style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; box-sizing: border-box;" readonly>
475 </div>
476 <div style="margin-bottom: 12px;">
477 <label style="display: block; margin-bottom: 5px; font-weight: 600; color: #333; font-size: 13px;">Email:</label>
478 <input type="email" id="autofill-email" placeholder="your.email@example.com" style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; box-sizing: border-box;" readonly>
479 </div>
480 <div style="margin-bottom: 12px;">
481 <label style="display: block; margin-bottom: 5px; font-weight: 600; color: #333; font-size: 13px;">Duration:</label>
482 <input type="text" id="autofill-duration" placeholder="e.g., 10am - 3pm" value="10am - 2.45pm" style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; box-sizing: border-box;">
483 <small style="color: #666; font-size: 11px;">Format: 10am - 3pm or 10:30am - 3:45pm</small>
484 </div>
485 <div style="margin-bottom: 15px;">
486 <label style="display: block; margin-bottom: 5px; font-weight: 600; color: #333; font-size: 13px;">Date (optional):</label>
487 <input type="text" id="autofill-date" placeholder="Leave blank for today" style="width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; font-size: 14px; box-sizing: border-box;">
488 <small style="color: #666; font-size: 11px;">Leave blank for today, or enter like "25/12/24"</small>
489 </div>
490 <button id="autofill-btn" style="width: 100%; padding: 10px; background: #0078d4; color: white; border: none; border-radius: 4px; font-size: 14px; font-weight: 600; cursor: pointer; transition: background 0.2s;">
491 Fill Form
492 </button>
493 <button id="clear-btn" style="width: 100%; padding: 10px; background: #d13438; color: white; border: none; border-radius: 4px; font-size: 14px; font-weight: 600; cursor: pointer; transition: background 0.2s; margin-top: 8px;">
494 Clear Form
495 </button>
496 <div id="autofill-status" style="margin-top: 10px; font-size: 12px; color: #666; text-align: center;"></div>
497 </div>
498
499 <!-- History Tab Content -->
500 <div id="history-content" style="display: none;">
501 <div style="margin-bottom: 15px;">
502 <button id="export-btn" style="width: 48%; padding: 8px; background: #107c10; color: white; border: none; border-radius: 4px; font-size: 13px; font-weight: 600; cursor: pointer; margin-right: 4%;">
503 Export
504 </button>
505 <button id="import-btn" style="width: 48%; padding: 8px; background: #8764b8; color: white; border: none; border-radius: 4px; font-size: 13px; font-weight: 600; cursor: pointer;">
506 Import
507 </button>
508 <input type="file" id="import-file" accept=".json" style="display: none;">
509 </div>
510 <div style="margin-bottom: 10px;">
511 <button id="clear-history-btn" style="width: 100%; padding: 8px; background: #d13438; color: white; border: none; border-radius: 4px; font-size: 13px; font-weight: 600; cursor: pointer;">
512 Clear All History
513 </button>
514 </div>
515 <div id="history-list" style="margin-top: 15px; max-height: 400px; overflow-y: auto;">
516 <p style="text-align: center; color: #666; font-size: 13px;">Loading history...</p>
517 </div>
518 </div>
519 </div>
520 `;
521
522 document.body.appendChild(panel);
523
524 // Person data
525 const personData = {
526 adrian: {
527 name: 'Adrian Chen',
528 email: 'adrian.ch3n@gmail.com'
529 },
530 charlene: {
531 name: 'Charlene Eng',
532 email: 'charlene.zita@gmail.com'
533 }
534 };
535
536 // Tab switching functionality
537 const tabForm = document.getElementById('tab-form');
538 const tabHistory = document.getElementById('tab-history');
539 const formContent = document.getElementById('form-content');
540 const historyContent = document.getElementById('history-content');
541
542 tabForm.addEventListener('click', () => {
543 tabForm.style.borderBottom = '3px solid #0078d4';
544 tabForm.style.color = '#0078d4';
545 tabHistory.style.borderBottom = '3px solid transparent';
546 tabHistory.style.color = '#666';
547 formContent.style.display = 'block';
548 historyContent.style.display = 'none';
549 });
550
551 tabHistory.addEventListener('click', async () => {
552 tabHistory.style.borderBottom = '3px solid #0078d4';
553 tabHistory.style.color = '#0078d4';
554 tabForm.style.borderBottom = '3px solid transparent';
555 tabForm.style.color = '#666';
556 historyContent.style.display = 'block';
557 formContent.style.display = 'none';
558 await loadHistoryView();
559 });
560
561 // Handle person selection
562 const personSelect = document.getElementById('autofill-person');
563 const nameInput = document.getElementById('autofill-name');
564 const emailInput = document.getElementById('autofill-email');
565
566 personSelect.addEventListener('change', () => {
567 const selectedPerson = personSelect.value;
568 if (selectedPerson && personData[selectedPerson]) {
569 nameInput.value = personData[selectedPerson].name;
570 emailInput.value = personData[selectedPerson].email;
571 } else {
572 nameInput.value = '';
573 emailInput.value = '';
574 }
575 });
576
577 // Add button hover effects
578 const btn = document.getElementById('autofill-btn');
579 btn.addEventListener('mouseenter', () => btn.style.background = '#005a9e');
580 btn.addEventListener('mouseleave', () => btn.style.background = '#0078d4');
581
582 const clearBtn = document.getElementById('clear-btn');
583 clearBtn.addEventListener('mouseenter', () => clearBtn.style.background = '#a4262c');
584 clearBtn.addEventListener('mouseleave', () => clearBtn.style.background = '#d13438');
585
586 // Add click handler for Fill Form button
587 btn.addEventListener('click', async () => {
588 const name = document.getElementById('autofill-name').value.trim();
589 const email = document.getElementById('autofill-email').value.trim();
590 const duration = document.getElementById('autofill-duration').value.trim();
591 const date = document.getElementById('autofill-date').value.trim();
592
593 if (!name || !email || !duration) {
594 alert('Please fill in all fields');
595 return;
596 }
597
598 const statusDiv = document.getElementById('autofill-status');
599 statusDiv.textContent = 'Filling form...';
600 statusDiv.style.color = '#0078d4';
601
602 btn.disabled = true;
603 btn.style.opacity = '0.6';
604
605 const success = await fillForm(name, email, duration, date);
606
607 if (success) {
608 statusDiv.textContent = '✓ Form filled successfully!';
609 statusDiv.style.color = '#107c10';
610 } else {
611 statusDiv.textContent = '✗ Error filling form';
612 statusDiv.style.color = '#d13438';
613 }
614
615 btn.disabled = false;
616 btn.style.opacity = '1';
617 });
618
619 // Add click handler for Clear Form button
620 clearBtn.addEventListener('click', async () => {
621 if (confirm('Are you sure you want to clear the form? This will reload the page.')) {
622 await clearForm();
623 }
624 });
625
626 // History management handlers
627 document.getElementById('export-btn').addEventListener('click', async () => {
628 const success = await exportHistory();
629 if (success) {
630 alert('History exported successfully!');
631 } else {
632 alert('Failed to export history');
633 }
634 });
635
636 document.getElementById('import-btn').addEventListener('click', () => {
637 document.getElementById('import-file').click();
638 });
639
640 document.getElementById('import-file').addEventListener('change', async (e) => {
641 const file = e.target.files[0];
642 if (!file) return;
643
644 try {
645 const fileContent = await file.text();
646 await importHistory(fileContent);
647 alert('History imported successfully!');
648 await loadHistoryView();
649 } catch (error) {
650 alert('Failed to import history: ' + error.message);
651 }
652 e.target.value = ''; // Reset file input
653 });
654
655 document.getElementById('clear-history-btn').addEventListener('click', async () => {
656 if (confirm('Are you sure you want to clear all history? This cannot be undone.')) {
657 const success = await clearHistory();
658 if (success) {
659 alert('History cleared successfully!');
660 await loadHistoryView();
661 } else {
662 alert('Failed to clear history');
663 }
664 }
665 });
666
667 console.log('Auto-fill panel created');
668 }
669
670 // Load and display history
671 async function loadHistoryView() {
672 const historyList = document.getElementById('history-list');
673 const history = await getHistory();
674
675 if (history.length === 0) {
676 historyList.innerHTML = '<p style="text-align: center; color: #666; font-size: 13px;">No submissions yet</p>';
677 return;
678 }
679
680 // Group by name and date
681 const grouped = groupHistoryByNameAndDate(history);
682
683 let html = '';
684 for (const name in grouped) {
685 html += `<div style="margin-bottom: 20px;">
686 <h4 style="margin: 0 0 10px 0; color: #0078d4; font-size: 15px; border-bottom: 2px solid #0078d4; padding-bottom: 5px;">${name}</h4>`;
687
688 for (const date in grouped[name]) {
689 const entries = grouped[name][date];
690 const formattedDate = formatDateToDDMMYYYY(date);
691 html += `<div style="margin-bottom: 10px; padding: 10px; background: #f5f5f5; border-radius: 4px;">
692 <div style="font-weight: 600; color: #333; font-size: 13px; margin-bottom: 5px;">📅 ${formattedDate}</div>`;
693
694 entries.forEach(entry => {
695 const formattedSubmittedDate = formatSubmittedDate(entry.submittedAt);
696 html += `<div style="margin-left: 15px; padding: 5px 0; border-bottom: 1px solid #ddd; font-size: 12px; display: flex; justify-content: space-between; align-items: center;">
697 <div style="flex: 1;">
698 <div style="color: #555;">⏰ ${entry.duration}</div>
699 <div style="color: #888; font-size: 11px;">Submitted: ${formattedSubmittedDate}</div>
700 </div>
701 <button class="delete-entry-btn" data-timestamp="${entry.timestamp}" style="background: #d13438; color: white; border: none; border-radius: 3px; padding: 4px 8px; font-size: 11px; cursor: pointer; margin-left: 10px;">
702 Delete
703 </button>
704 </div>`;
705 });
706
707 html += '</div>';
708 }
709
710 html += '</div>';
711 }
712
713 historyList.innerHTML = html;
714
715 // Add event listeners to delete buttons
716 const deleteButtons = historyList.querySelectorAll('.delete-entry-btn');
717 deleteButtons.forEach(button => {
718 button.addEventListener('click', async (e) => {
719 const timestamp = e.target.getAttribute('data-timestamp');
720 if (confirm('Are you sure you want to delete this entry?')) {
721 const success = await deleteHistoryEntry(timestamp);
722 if (success) {
723 await loadHistoryView();
724 } else {
725 alert('Failed to delete entry');
726 }
727 }
728 });
729
730 // Add hover effect
731 button.addEventListener('mouseenter', () => button.style.background = '#a4262c');
732 button.addEventListener('mouseleave', () => button.style.background = '#d13438');
733 });
734 }
735
736 // Wait for page to load
737 function init() {
738 if (document.readyState === 'loading') {
739 document.addEventListener('DOMContentLoaded', () => {
740 setTimeout(createPanel, 1000);
741 });
742 } else {
743 setTimeout(createPanel, 1000);
744 }
745 }
746
747 init();
748})();