Feat(checkbox): Initial upload
This commit is contained in:
182
static/scripts/parts-bulk-operations.js
Normal file
182
static/scripts/parts-bulk-operations.js
Normal 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);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user