- Express + EJS server-rendered app - Supabase PostgreSQL database - Auth: username/password with bcrypt - Dashboard with business stats - Load CRUD with filters - WhatsApp message parser - Payment tracking - Shipper & vehicle management - Reports (monthly, top shippers, routes) - Government-app aesthetic (tricolor theme) - Dark mode support - Docker + Coolify deployment ready - Seed data from existing business ledger (88 loads, 41 shippers, 70 vehicles)
59 lines
1.7 KiB
JavaScript
59 lines
1.7 KiB
JavaScript
// FreightDesk — Client-side JavaScript
|
|
|
|
// Theme toggle
|
|
function toggleTheme() {
|
|
const html = document.documentElement;
|
|
const current = html.getAttribute('data-theme');
|
|
const next = current === 'dark' ? 'light' : 'dark';
|
|
html.setAttribute('data-theme', next);
|
|
localStorage.setItem('fd-theme', next);
|
|
}
|
|
|
|
// Restore theme
|
|
(function() {
|
|
const saved = localStorage.getItem('fd-theme');
|
|
if (saved) document.documentElement.setAttribute('data-theme', saved);
|
|
})();
|
|
|
|
// Auto-hide alerts after 5 seconds
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const alerts = document.querySelectorAll('.alert');
|
|
alerts.forEach(function(alert) {
|
|
setTimeout(function() {
|
|
alert.style.opacity = '0';
|
|
alert.style.transition = 'opacity 0.5s';
|
|
setTimeout(function() { alert.remove(); }, 500);
|
|
}, 5000);
|
|
});
|
|
});
|
|
|
|
// Confirm delete actions
|
|
document.querySelectorAll('form[onsubmit]').forEach(function(form) {
|
|
form.addEventListener('submit', function(e) {
|
|
const msg = form.getAttribute('onsubmit');
|
|
if (msg && msg.includes('confirm')) {
|
|
const question = msg.match(/confirm\('(.+?)'\)/);
|
|
if (question && !confirm(question[1])) {
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// WhatsApp parser (inline function for form page)
|
|
// parseWhatsApp() and applyParsed() are defined inline in the form view
|
|
|
|
// Format number as INR
|
|
function formatINR(num) {
|
|
if (num === null || num === undefined || isNaN(num)) return '—';
|
|
return '₹' + parseFloat(num).toLocaleString('en-IN');
|
|
}
|
|
|
|
// Debounce helper
|
|
function debounce(fn, ms) {
|
|
let timer;
|
|
return function(...args) {
|
|
clearTimeout(timer);
|
|
timer = setTimeout(() => fn.apply(this, args), ms);
|
|
};
|
|
}
|