@@ -47,7 +47,24 @@ export const history = new Elysia().use(userService).get(
4747 ` }
4848 >
4949 < article class = "article" >
50- < h1 class = "mb-4 text-xl" > Results</ h1 >
50+ < div class = "mb-4 flex items-center justify-between" >
51+ < h1 class = "text-xl" > Results</ h1 >
52+ < div id = "delete-selected-container" >
53+ < button
54+ id = "delete-selected-btn"
55+ class = { `
56+ flex btn-secondary flex-row gap-2 text-contrast
57+ disabled:cursor-not-allowed disabled:opacity-50
58+ ` }
59+ disabled
60+ >
61+ < DeleteIcon /> { " " }
62+ < span >
63+ Delete Selected (< span id = "selected-count" > 0</ span > )
64+ </ span >
65+ </ button >
66+ </ div >
67+ </ div >
5168 < table
5269 class = { `
5370 w-full table-auto overflow-y-auto rounded bg-neutral-900 text-left
@@ -57,6 +74,19 @@ export const history = new Elysia().use(userService).get(
5774 >
5875 < thead >
5976 < tr >
77+ < th
78+ class = { `
79+ px-2 py-2
80+ sm:px-4
81+ ` }
82+ >
83+ < input
84+ type = "checkbox"
85+ id = "select-all"
86+ class = "h-4 w-4 cursor-pointer"
87+ title = "Select all"
88+ />
89+ </ th >
6090 < th
6191 class = { `
6292 px-2 py-2
@@ -112,6 +142,14 @@ export const history = new Elysia().use(userService).get(
112142 { userJobs . map ( ( job ) => (
113143 < >
114144 < tr id = { `job-row-${ job . id } ` } >
145+ < td >
146+ < input
147+ type = "checkbox"
148+ class = "h-4 w-4 cursor-pointer"
149+ data-checkbox-type = "job"
150+ data-job-id = { job . id }
151+ />
152+ </ td >
115153 < td class = "job-details-toggle cursor-pointer" data-job-id = { job . id } >
116154 < svg
117155 id = { `arrow-${ job . id } ` }
@@ -159,7 +197,7 @@ export const history = new Elysia().use(userService).get(
159197 </ td >
160198 </ tr >
161199 < tr id = { `details-${ job . id } ` } class = "hidden" >
162- < td colspan = "6 " >
200+ < td colspan = "7 " >
163201 < div class = "p-2 text-sm text-neutral-500" >
164202 < div class = "mb-1 font-semibold" > Detailed File Information:</ div >
165203 { job . files_detailed . map ( ( file : Filename ) => (
@@ -196,26 +234,97 @@ export const history = new Elysia().use(userService).get(
196234 < script >
197235 { `
198236 document.addEventListener('DOMContentLoaded', () => {
237+ // Expand/collapse job details
199238 const toggles = document.querySelectorAll('.job-details-toggle');
200239 toggles.forEach(toggle => {
201240 toggle.addEventListener('click', function() {
202241 const jobId = this.dataset.jobId;
203242 const detailsRow = document.getElementById(\`details-\${jobId}\`);
204- // The arrow SVG itself has the ID arrow-\${jobId}
205243 const arrow = document.getElementById(\`arrow-\${jobId}\`);
206244
207245 if (detailsRow && arrow) {
208246 detailsRow.classList.toggle("hidden");
209247 if (detailsRow.classList.contains("hidden")) {
210- // Right-facing arrow (collapsed)
211248 arrow.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />';
212249 } else {
213- // Down-facing arrow (expanded)
214250 arrow.innerHTML = '<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />';
215251 }
216252 }
217253 });
218254 });
255+
256+ // Checkbox management
257+ const selectAllCheckbox = document.getElementById('select-all');
258+ const jobCheckboxes = document.querySelectorAll('[data-checkbox-type="job"]');
259+ const deleteSelectedBtn = document.getElementById('delete-selected-btn');
260+ const deleteSelectedContainer = document.getElementById('delete-selected-container');
261+ const selectedCountSpan = document.getElementById('selected-count');
262+
263+ function updateDeleteButton() {
264+ const checkedBoxes = Array.from(jobCheckboxes).filter(cb => cb.checked);
265+ if (checkedBoxes.length > 0) {
266+ deleteSelectedBtn.disabled = false;
267+ selectedCountSpan.textContent = checkedBoxes.length;
268+ } else {
269+ deleteSelectedBtn.disabled = true;
270+ selectedCountSpan.textContent = '0';
271+ }
272+ }
273+
274+ selectAllCheckbox?.addEventListener('change', function() {
275+ jobCheckboxes.forEach(checkbox => {
276+ checkbox.checked = this.checked;
277+ });
278+ updateDeleteButton();
279+ });
280+
281+ jobCheckboxes.forEach(checkbox => {
282+ checkbox.addEventListener('change', function() {
283+ const allChecked = Array.from(jobCheckboxes).every(cb => cb.checked);
284+ const someChecked = Array.from(jobCheckboxes).some(cb => cb.checked);
285+ if (selectAllCheckbox) {
286+ selectAllCheckbox.checked = allChecked;
287+ selectAllCheckbox.indeterminate = someChecked && !allChecked;
288+ }
289+ updateDeleteButton();
290+ });
291+ });
292+
293+ deleteSelectedBtn?.addEventListener('click', async function() {
294+ const checkedBoxes = Array.from(jobCheckboxes).filter(cb => cb.checked);
295+ const jobIds = checkedBoxes.map(cb => cb.dataset.jobId);
296+
297+ if (jobIds.length === 0) return;
298+
299+ const confirmed = confirm(\`Are you sure you want to delete \${jobIds.length} job(s)? This action cannot be undone.\`);
300+ if (!confirmed) return;
301+
302+ try {
303+ const response = await fetch('${ WEBROOT } /delete-multiple', {
304+ method: 'POST',
305+ headers: {
306+ 'Content-Type': 'application/json',
307+ },
308+ body: JSON.stringify({ jobIds }),
309+ });
310+
311+ if (!response.ok) {
312+ throw new Error(\`HTTP error! status: \${response.status}\`);
313+ }
314+
315+ const result = await response.json();
316+
317+ if (result.success || result.deleted > 0) {
318+ alert(\`Successfully deleted \${result.deleted} job(s).\${result.failed > 0 ? \` Failed to delete \${result.failed} job(s).\` : ''}\`);
319+ window.location.reload();
320+ } else {
321+ alert('Failed to delete jobs. Please try again.');
322+ }
323+ } catch (error) {
324+ console.error('Error deleting jobs:', error);
325+ alert('An error occurred while deleting jobs. Please try again.');
326+ }
327+ });
219328 });
220329 ` }
221330 </ script >
0 commit comments