@@ -48,6 +48,17 @@ export const history = new Elysia().use(userService).get(
4848 >
4949 < article class = "article" >
5050 < h1 class = "mb-4 text-xl" > Results</ h1 >
51+ < div id = "delete-selected-container" class = "mb-4 hidden" >
52+ < button
53+ id = "delete-selected-btn"
54+ class = { `
55+ rounded bg-red-600 px-4 py-2 text-white transition-colors
56+ hover:bg-red-700
57+ ` }
58+ >
59+ Delete Selected (< span id = "selected-count" > 0</ span > )
60+ </ button >
61+ </ div >
5162 < table
5263 class = { `
5364 w-full table-auto overflow-y-auto rounded bg-neutral-900 text-left
@@ -57,6 +68,19 @@ export const history = new Elysia().use(userService).get(
5768 >
5869 < thead >
5970 < tr >
71+ < th
72+ class = { `
73+ px-2 py-2
74+ sm:px-4
75+ ` }
76+ >
77+ < input
78+ type = "checkbox"
79+ id = "select-all"
80+ class = "h-4 w-4 cursor-pointer"
81+ title = "Select all"
82+ />
83+ </ th >
6084 < th
6185 class = { `
6286 px-2 py-2
@@ -112,6 +136,13 @@ export const history = new Elysia().use(userService).get(
112136 { userJobs . map ( ( job ) => (
113137 < >
114138 < tr id = { `job-row-${ job . id } ` } >
139+ < td >
140+ < input
141+ type = "checkbox"
142+ class = "job-checkbox h-4 w-4 cursor-pointer"
143+ data-job-id = { job . id }
144+ />
145+ </ td >
115146 < td class = "job-details-toggle cursor-pointer" data-job-id = { job . id } >
116147 < svg
117148 id = { `arrow-${ job . id } ` }
@@ -155,7 +186,7 @@ export const history = new Elysia().use(userService).get(
155186 </ td >
156187 </ tr >
157188 < tr id = { `details-${ job . id } ` } class = "hidden" >
158- < td colspan = "6 " >
189+ < td colspan = "7 " >
159190 < div class = "p-2 text-sm text-neutral-500" >
160191 < div class = "mb-1 font-semibold" > Detailed File Information:</ div >
161192 { job . files_detailed . map ( ( file : Filename ) => (
@@ -192,26 +223,92 @@ export const history = new Elysia().use(userService).get(
192223 < script >
193224 { `
194225 document.addEventListener('DOMContentLoaded', () => {
226+ // Expand/collapse job details
195227 const toggles = document.querySelectorAll('.job-details-toggle');
196228 toggles.forEach(toggle => {
197229 toggle.addEventListener('click', function() {
198230 const jobId = this.dataset.jobId;
199231 const detailsRow = document.getElementById(\`details-\${jobId}\`);
200- // The arrow SVG itself has the ID arrow-\${jobId}
201232 const arrow = document.getElementById(\`arrow-\${jobId}\`);
202233
203234 if (detailsRow && arrow) {
204235 detailsRow.classList.toggle("hidden");
205236 if (detailsRow.classList.contains("hidden")) {
206- // Right-facing arrow (collapsed)
207237 arrow.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />';
208238 } else {
209- // Down-facing arrow (expanded)
210239 arrow.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />';
211240 }
212241 }
213242 });
214243 });
244+
245+ // Checkbox management
246+ const selectAllCheckbox = document.getElementById('select-all');
247+ const jobCheckboxes = document.querySelectorAll('.job-checkbox');
248+ const deleteSelectedBtn = document.getElementById('delete-selected-btn');
249+ const deleteSelectedContainer = document.getElementById('delete-selected-container');
250+ const selectedCountSpan = document.getElementById('selected-count');
251+
252+ function updateDeleteButton() {
253+ const checkedBoxes = Array.from(jobCheckboxes).filter(cb => cb.checked);
254+ if (checkedBoxes.length > 0) {
255+ deleteSelectedContainer.classList.remove('hidden');
256+ selectedCountSpan.textContent = checkedBoxes.length;
257+ } else {
258+ deleteSelectedContainer.classList.add('hidden');
259+ }
260+ }
261+
262+ selectAllCheckbox?.addEventListener('change', function() {
263+ jobCheckboxes.forEach(checkbox => {
264+ checkbox.checked = this.checked;
265+ });
266+ updateDeleteButton();
267+ });
268+
269+ jobCheckboxes.forEach(checkbox => {
270+ checkbox.addEventListener('change', function() {
271+ const allChecked = Array.from(jobCheckboxes).every(cb => cb.checked);
272+ const someChecked = Array.from(jobCheckboxes).some(cb => cb.checked);
273+ if (selectAllCheckbox) {
274+ selectAllCheckbox.checked = allChecked;
275+ selectAllCheckbox.indeterminate = someChecked && !allChecked;
276+ }
277+ updateDeleteButton();
278+ });
279+ });
280+
281+ deleteSelectedBtn?.addEventListener('click', async function() {
282+ const checkedBoxes = Array.from(jobCheckboxes).filter(cb => cb.checked);
283+ const jobIds = checkedBoxes.map(cb => cb.dataset.jobId);
284+
285+ if (jobIds.length === 0) return;
286+
287+ const confirmed = confirm(\`Are you sure you want to delete \${jobIds.length} job(s)? This action cannot be undone.\`);
288+ if (!confirmed) return;
289+
290+ try {
291+ const response = await fetch('${ WEBROOT } /delete-multiple', {
292+ method: 'POST',
293+ headers: {
294+ 'Content-Type': 'application/json',
295+ },
296+ body: JSON.stringify({ jobIds }),
297+ });
298+
299+ const result = await response.json();
300+
301+ if (result.success || result.deleted > 0) {
302+ alert(\`Successfully deleted \${result.deleted} job(s).\${result.failed > 0 ? \` Failed to delete \${result.failed} job(s).\` : ''}\`);
303+ window.location.reload();
304+ } else {
305+ alert('Failed to delete jobs. Please try again.');
306+ }
307+ } catch (error) {
308+ console.error('Error deleting jobs:', error);
309+ alert('An error occurred while deleting jobs. Please try again.');
310+ }
311+ });
215312 });
216313 ` }
217314 </ script >
0 commit comments