Files
newznab-tmux/resources/js/alpine/components/admin/release-list.js
2026-06-24 18:22:54 +02:00

192 lines
5.6 KiB
JavaScript

/**
* Alpine.data('adminReleaseList') - Admin release list bulk selection and category change
*/
import Alpine from '@alpinejs/csp';
Alpine.data('adminReleaseList', () => ({
allChecked: false,
selectedCount: 0,
rootEl: null,
init() {
this.rootEl = this.$root;
this.syncSelectionState();
},
componentRoot() {
return this.rootEl || this.$root;
},
releaseCheckboxes() {
const root = this.componentRoot();
return root ? [...root.querySelectorAll('.release-checkbox')] : [];
},
setAllSelection(checked) {
const boxes = this.releaseCheckboxes();
boxes.forEach(cb => {
cb.checked = checked;
});
this.allChecked = checked && boxes.length > 0;
this.selectedCount = checked ? boxes.length : 0;
},
selectAll() {
this.setAllSelection(true);
},
clearSelection() {
this.setAllSelection(false);
},
syncSelectionState() {
const boxes = this.releaseCheckboxes();
const checkedCount = boxes.filter(cb => cb.checked).length;
this.selectedCount = checkedCount;
this.allChecked = boxes.length > 0 && checkedCount === boxes.length;
},
onCheckboxChange() {
this.syncSelectionState();
},
showModal(options) {
if (typeof window.showConfirm === 'function') {
window.showConfirm(options);
return;
}
if (options.onConfirm) {
options.onConfirm();
}
},
showToast(message, type) {
Alpine.store('toast').show(message, type);
},
deleteRelease(e) {
e.preventDefault();
const button = e.currentTarget;
const deleteUrl = button.dataset.deleteUrl;
if (!deleteUrl) {
this.showToast('Release delete URL is missing.', 'error');
return;
}
this.showModal({
title: 'Delete Release',
message: 'Are you sure you want to delete this release?',
details: 'This action cannot be undone.',
type: 'danger',
confirmText: 'Delete',
cancelText: 'Cancel',
onConfirm: () => this.submitReleaseDelete(button, deleteUrl),
});
},
submitReleaseDelete(button, deleteUrl) {
const csrf = document.querySelector('meta[name="csrf-token"]')?.content;
if (!csrf) {
this.showToast('Unable to delete release: CSRF token is missing.', 'error');
return;
}
button.disabled = true;
button.setAttribute('aria-busy', 'true');
this.showToast('Deleting release...', 'info');
fetch(deleteUrl, {
method: 'POST',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-TOKEN': csrf,
'Accept': 'application/json',
},
})
.then(response => response.json().catch(() => ({})).then(data => ({ response, data })))
.then(({ response, data }) => {
if (!response.ok || data.success === false) {
throw new Error(data.message || 'Error deleting release: ' + response.status);
}
const row = button.closest('tr');
if (row) {
row.style.transition = 'opacity 0.3s';
row.style.opacity = '0';
setTimeout(() => {
row.remove();
this.syncSelectionState();
}, 300);
} else {
this.syncSelectionState();
}
this.showToast(data.message || 'Release deleted successfully', 'success');
})
.catch(error => {
button.disabled = false;
this.showToast(error.message || 'Error deleting release.', 'error');
})
.finally(() => {
button.removeAttribute('aria-busy');
});
},
validateBulkAction(e) {
e.preventDefault();
const form = e.target;
const root = this.componentRoot();
const categorySelect = root?.querySelector('select[name="categories_id"]');
const categoryId = categorySelect?.value;
const categoryName = categorySelect?.selectedOptions?.[0]?.text?.trim() || '';
const checkedCount = this.releaseCheckboxes().filter(cb => cb.checked).length;
if (!categoryId || categoryId === '-1') {
this.showModal({
title: 'Category required',
message: 'Please select a category to assign to the selected releases.',
type: 'warning',
confirmText: 'OK',
cancelText: 'Close',
onConfirm: function() {},
});
return;
}
if (checkedCount === 0) {
this.showModal({
title: 'No releases selected',
message: 'Please select at least one release before changing its category.',
type: 'warning',
confirmText: 'OK',
cancelText: 'Close',
onConfirm: function() {},
});
return;
}
this.showModal({
title: 'Change category',
message: 'Change category for ' + checkedCount + ' release(s)?',
details: 'Assign to: ' + categoryName + '. The database and search index will be updated.',
type: 'warning',
confirmText: 'Change category',
cancelText: 'Cancel',
onConfirm: function() {
form.submit();
},
});
},
}));