Feat(checkbox): Initial upload

This commit is contained in:
2025-09-26 11:47:15 +02:00
parent a769e5464b
commit f03fd82be1
15 changed files with 403 additions and 10 deletions

View File

@@ -0,0 +1,182 @@
// Bulk operations for parts in set details page
class PartsBulkOperations {
constructor(accordionId) {
this.accordionId = accordionId;
this.setupModal();
this.setupEventListeners();
}
setupModal() {
// Create Bootstrap modal if it doesn't exist
if (!document.getElementById('partsConfirmModal')) {
const modalHTML = `
<div class="modal fade" id="partsConfirmModal" tabindex="-1" aria-labelledby="partsConfirmModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="partsConfirmModalLabel">Confirm Action</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p id="partsConfirmModalMessage"></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" id="partsConfirmModalConfirm">Confirm</button>
</div>
</div>
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', modalHTML);
}
}
setupEventListeners() {
// Mark all as missing (only if missing parts are not hidden)
const markAllMissingBtn = document.getElementById(`mark-all-missing-${this.accordionId}`);
if (markAllMissingBtn) {
markAllMissingBtn.addEventListener('click', (e) => {
e.preventDefault();
this.confirmAndExecute(
'Mark all parts as missing?',
'This will set the missing count to the maximum quantity for all parts in this section.',
() => this.markAllMissing()
);
});
}
// Clear all missing (only if missing parts are not hidden)
const clearAllMissingBtn = document.getElementById(`clear-all-missing-${this.accordionId}`);
if (clearAllMissingBtn) {
clearAllMissingBtn.addEventListener('click', (e) => {
e.preventDefault();
this.confirmAndExecute(
'Clear all missing parts?',
'This will clear the missing field for all parts in this section.',
() => this.clearAllMissing()
);
});
}
// Check all checkboxes (only if checked parts are not hidden)
const checkAllBtn = document.getElementById(`check-all-${this.accordionId}`);
if (checkAllBtn) {
checkAllBtn.addEventListener('click', (e) => {
e.preventDefault();
this.checkAll();
});
}
// Uncheck all checkboxes (only if checked parts are not hidden)
const uncheckAllBtn = document.getElementById(`uncheck-all-${this.accordionId}`);
if (uncheckAllBtn) {
uncheckAllBtn.addEventListener('click', (e) => {
e.preventDefault();
this.uncheckAll();
});
}
}
confirmAndExecute(title, message, callback) {
const modal = document.getElementById('partsConfirmModal');
const modalTitle = document.getElementById('partsConfirmModalLabel');
const modalMessage = document.getElementById('partsConfirmModalMessage');
const confirmBtn = document.getElementById('partsConfirmModalConfirm');
// Set modal content
modalTitle.textContent = title;
modalMessage.textContent = message;
// Remove any existing event listeners and add new one
const newConfirmBtn = confirmBtn.cloneNode(true);
confirmBtn.parentNode.replaceChild(newConfirmBtn, confirmBtn);
newConfirmBtn.addEventListener('click', () => {
const modalInstance = bootstrap.Modal.getInstance(modal);
modalInstance.hide();
callback();
});
// Show modal
const modalInstance = new bootstrap.Modal(modal);
modalInstance.show();
}
markAllMissing() {
const accordionElement = document.getElementById(this.accordionId);
if (!accordionElement) return;
// Find all rows in this accordion
const rows = accordionElement.querySelectorAll('tbody tr');
rows.forEach(row => {
// Find the quantity cell (usually 4th column)
const quantityCell = row.cells[3]; // Index 3 for quantity column
const missingInput = row.querySelector('input[id*="-missing-"]');
if (quantityCell && missingInput) {
// Extract quantity from cell text content
const quantityText = quantityCell.textContent.trim();
const quantity = parseInt(quantityText) || 1; // Default to 1 if can't parse
if (missingInput.value !== quantity.toString()) {
missingInput.value = quantity.toString();
// Trigger change event to activate BrickChanger
missingInput.dispatchEvent(new Event('change', { bubbles: true }));
}
}
});
}
clearAllMissing() {
const accordionElement = document.getElementById(this.accordionId);
if (!accordionElement) return;
const missingInputs = accordionElement.querySelectorAll('input[id*="-missing-"]');
missingInputs.forEach(input => {
if (input.value !== '') {
input.value = '';
// Trigger change event to activate BrickChanger
input.dispatchEvent(new Event('change', { bubbles: true }));
}
});
}
checkAll() {
const accordionElement = document.getElementById(this.accordionId);
if (!accordionElement) return;
const checkboxes = accordionElement.querySelectorAll('input[id*="-checked-"][type="checkbox"]');
checkboxes.forEach(checkbox => {
if (!checkbox.checked) {
checkbox.checked = true;
// Trigger change event to activate BrickChanger
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
}
});
}
uncheckAll() {
const accordionElement = document.getElementById(this.accordionId);
if (!accordionElement) return;
const checkboxes = accordionElement.querySelectorAll('input[id*="-checked-"][type="checkbox"]');
checkboxes.forEach(checkbox => {
if (checkbox.checked) {
checkbox.checked = false;
// Trigger change event to activate BrickChanger
checkbox.dispatchEvent(new Event('change', { bubbles: true }));
}
});
}
}
// Initialize bulk operations for all part accordions when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
// Find all hamburger menus and initialize bulk operations
const hamburgerMenus = document.querySelectorAll('button[id^="hamburger-"]');
hamburgerMenus.forEach(button => {
const accordionId = button.id.replace('hamburger-', '');
new PartsBulkOperations(accordionId);
});
});

View File

@@ -50,6 +50,67 @@
max-width: 150px;
}
/* Checkbox column width constraint */
.table-td-input:has(.form-check-input[type="checkbox"]) {
width: 120px;
max-width: 120px;
min-width: 120px;
}
/* Reserve space for status icon to prevent layout shift */
.form-check-label i[id^="status-"] {
display: inline-block;
width: 1.2em;
text-align: center;
margin-left: 0.25rem;
}
/* Hamburger menu styling */
.table th .dropdown {
position: relative;
}
.table th .dropdown-toggle {
border-radius: 0.375rem;
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
line-height: 1.25rem;
border-color: #6c757d;
}
.table th .dropdown-toggle:focus {
box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25);
}
.table th .dropdown-toggle:hover {
background-color: #f8f9fa;
border-color: #6c757d;
}
/* Style dropdown items */
.dropdown-menu .dropdown-header {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
color: #6c757d;
padding: 0.25rem 1rem;
}
.dropdown-menu .dropdown-item {
font-size: 0.875rem;
padding: 0.5rem 1rem;
}
.dropdown-menu .dropdown-item:hover {
background-color: #f8f9fa;
color: #212529;
}
.dropdown-menu .dropdown-item i {
width: 1.25rem;
text-align: center;
}
/* Fixes for sortable.js */
.sortable {
--th-color: #000 !important;