This commit is contained in:
HotSwapp
2025-08-09 16:37:57 -05:00
parent 5f74243c8c
commit c2f3c4411d
35 changed files with 9209 additions and 4633 deletions

View File

@@ -20,17 +20,7 @@ async function initializeApp() {
window.keyboardShortcuts.initialize();
}
// Initialize tooltips
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
// Initialize popovers
const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]'));
popoverTriggerList.map(function (popoverTriggerEl) {
return new bootstrap.Popover(popoverTriggerEl);
});
// Remove Bootstrap-dependent tooltips/popovers; use native title/tooltips if needed
// Add form validation classes
initializeFormValidation();
@@ -44,19 +34,19 @@ async function initializeApp() {
// Form validation
function initializeFormValidation() {
// Add Bootstrap validation styles
const forms = document.querySelectorAll('form.needs-validation');
// Native validation handling without Bootstrap classes
const forms = document.querySelectorAll('form');
forms.forEach(form => {
form.addEventListener('submit', function(event) {
if (!form.checkValidity()) {
event.preventDefault();
event.stopPropagation();
form.reportValidity();
}
form.classList.add('was-validated');
});
});
// Real-time validation for specific fields
// Real-time validation for required fields (Tailwind styles)
const requiredFields = document.querySelectorAll('input[required], select[required], textarea[required]');
requiredFields.forEach(field => {
field.addEventListener('blur', function() {
@@ -67,15 +57,8 @@ function initializeFormValidation() {
function validateField(field) {
const isValid = field.checkValidity();
field.classList.remove('is-valid', 'is-invalid');
field.classList.add(isValid ? 'is-valid' : 'is-invalid');
// Show/hide custom feedback
const feedback = field.parentNode.querySelector('.invalid-feedback');
if (feedback) {
feedback.classList.toggle('hidden', isValid);
feedback.classList.toggle('visible', !isValid);
}
field.setAttribute('aria-invalid', String(!isValid));
field.classList.toggle('border-danger-500', !isValid);
}
// API helpers
@@ -157,45 +140,18 @@ function logout() {
window.location.href = '/login';
}
// Notification system
// Notification system (delegates to shared alerts utility)
function showNotification(message, type = 'info', duration = 5000) {
const notificationContainer = getOrCreateNotificationContainer();
const notification = document.createElement('div');
notification.className = `alert alert-${type} alert-dismissible fade show`;
notification.setAttribute('role', 'alert');
notification.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
`;
notificationContainer.appendChild(notification);
// Auto-dismiss after duration
if (duration > 0) {
setTimeout(() => {
notification.remove();
}, duration);
if (window.alerts && typeof window.alerts.show === 'function') {
return window.alerts.show(message, type, { duration });
}
return notification;
}
function getOrCreateNotificationContainer() {
let container = document.querySelector('#notification-container');
if (!container) {
container = document.createElement('div');
container.id = 'notification-container';
container.className = 'position-fixed top-0 end-0 p-3';
container.classList.add('notification-container');
document.body.appendChild(container);
}
return container;
// Fallback if alerts module not yet loaded
return alert(String(message));
}
// Loading states
function showLoading(element, text = 'Loading...') {
const spinner = `<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>`;
const spinner = `<span class="inline-block animate-spin w-4 h-4 border-2 border-current border-t-transparent rounded-full mr-2"></span>`;
const originalContent = element.innerHTML;
element.innerHTML = `${spinner}${text}`;
element.disabled = true;
@@ -271,11 +227,12 @@ function addRowSelection(table) {
tbody.addEventListener('click', function(e) {
const row = e.target.closest('tr');
if (row && e.target.type !== 'checkbox') {
row.classList.toggle('table-active');
const isSelected = row.classList.toggle('bg-neutral-100');
row.classList.toggle('dark:bg-neutral-700', isSelected);
// Trigger custom event
const event = new CustomEvent('rowSelect', {
detail: { row, selected: row.classList.contains('table-active') }
detail: { row, selected: isSelected }
});
table.dispatchEvent(event);
}
@@ -342,18 +299,18 @@ function initializeSearch(searchInput, resultsContainer, searchFunction) {
function displaySearchResults(container, results) {
if (!results || results.length === 0) {
container.innerHTML = '<p class="text-muted">No results found</p>';
container.innerHTML = '<p class="text-neutral-500">No results found</p>';
return;
}
const resultsHtml = results.map(result => `
<div class="search-result p-2 border-bottom">
<div class="d-flex justify-content-between">
<div class="flex justify-between">
<div>
<strong>${result.title}</strong>
<small class="text-muted d-block">${result.description}</small>
<small class="text-neutral-500 block">${result.description}</small>
</div>
<span class="badge bg-secondary">${result.type}</span>
<span class="inline-block px-2 py-0.5 text-xs rounded bg-neutral-200 text-neutral-700">${result.type}</span>
</div>
</div>
`).join('');