thors1's picture
🐳 11/04 - 18:15 - 🚨 ULTRA FINAL MICRO POLISH — FINANCIAL OS (10/10 LOCK) CRITICAL: DO NOT change: * layout * spacing system * structure * typography scale ONLY refine: * visual weight * softness *
f30e632 verified
// Initialize Lucide icons
lucide.createIcons();
// State management
let isSidebarCollapsed = false;
let isBillingExpanded = true;
let lineItemCount = 0;
let currentPatient = null;
// Mock patient data for search
const mockPatients = [
{ id: 'PT-1001', name: 'John Smith', phone: '+1 (555) 123-4567', image: 'https://static.photos/people/100x100/10' },
{ id: 'PT-1002', name: 'Emma Wilson', phone: '+1 (555) 234-5678', image: 'https://static.photos/people/100x100/20' },
{ id: 'PT-1003', name: 'Michael Brown', phone: '+1 (555) 345-6789', image: 'https://static.photos/people/100x100/30' },
{ id: 'PT-1004', name: 'Sarah Johnson', phone: '+1 (555) 456-7890', image: 'https://static.photos/people/100x100/40' },
{ id: 'PT-1005', name: 'David Lee', phone: '+1 (555) 567-8901', image: 'https://static.photos/people/100x100/50' }
];
// Initialize on load
document.addEventListener('DOMContentLoaded', () => {
// Set default dates
const today = new Date().toISOString().split('T')[0];
document.getElementById('invoiceDate').value = today;
const dueDate = new Date();
dueDate.setDate(dueDate.getDate() + 30);
document.getElementById('dueDate').value = dueDate.toISOString().split('T')[0];
// Add initial line item
addLineItem();
});
// Sidebar toggle for mobile
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('mobileOverlay');
const isOpen = !sidebar.classList.contains('-translate-x-full');
if (isOpen) {
sidebar.classList.add('-translate-x-full');
overlay.classList.add('hidden');
document.body.style.overflow = '';
} else {
sidebar.classList.remove('-translate-x-full');
overlay.classList.remove('hidden');
document.body.style.overflow = 'hidden';
}
}
// Sidebar toggle for desktop
function toggleSidebarDesktop() {
const sidebar = document.getElementById('sidebar');
const mainWrapper = document.getElementById('mainWrapper');
const desktopToggle = document.getElementById('desktopToggle');
isSidebarCollapsed = !isSidebarCollapsed;
if (isSidebarCollapsed) {
sidebar.classList.add('sidebar-collapsed');
sidebar.style.width = '5rem';
mainWrapper.style.marginLeft = '5rem';
desktopToggle.innerHTML = '<i data-lucide="panel-right" class="w-5 h-5"></i>';
} else {
sidebar.classList.remove('sidebar-collapsed');
sidebar.style.width = '16rem';
mainWrapper.style.marginLeft = '16rem';
desktopToggle.innerHTML = '<i data-lucide="panel-left" class="w-5 h-5"></i>';
}
lucide.createIcons();
// Handle billing submenu visibility
const submenu = document.getElementById('billingSubmenu');
if (isSidebarCollapsed) {
submenu.style.display = 'none';
} else if (isBillingExpanded) {
submenu.style.display = 'block';
}
}
// Billing submenu toggle
function toggleBillingSubmenu() {
if (isSidebarCollapsed) return;
const submenu = document.getElementById('billingSubmenu');
const chevron = document.getElementById('billingChevron');
isBillingExpanded = !isBillingExpanded;
if (isBillingExpanded) {
submenu.style.display = 'block';
chevron.style.transform = 'rotate(0deg)';
} else {
submenu.style.display = 'none';
chevron.style.transform = 'rotate(-90deg)';
}
}
// Patient type toggle
function setPatientType(type) {
const btnExisting = document.getElementById('btnExisting');
const btnNew = document.getElementById('btnNew');
const existingForm = document.getElementById('existingPatientForm');
const newForm = document.getElementById('newPatientForm');
if (type === 'existing') {
btnExisting.classList.add('bg-white', 'text-gray-900', 'shadow-sm');
btnExisting.classList.remove('text-gray-600');
btnNew.classList.remove('bg-white', 'text-gray-900', 'shadow-sm');
btnNew.classList.add('text-gray-600');
existingForm.classList.remove('hidden');
newForm.classList.add('hidden');
} else {
btnNew.classList.add('bg-white', 'text-gray-900', 'shadow-sm');
btnNew.classList.remove('text-gray-600');
btnExisting.classList.remove('bg-white', 'text-gray-900', 'shadow-sm');
btnExisting.classList.add('text-gray-600');
newForm.classList.remove('hidden');
existingForm.classList.add('hidden');
}
}
// Patient search functionality
function searchPatients(query) {
const resultsDiv = document.getElementById('searchResults');
if (query.length < 2) {
resultsDiv.classList.add('hidden');
return;
}
const filtered = mockPatients.filter(p =>
p.name.toLowerCase().includes(query.toLowerCase()) ||
p.id.toLowerCase().includes(query.toLowerCase()) ||
p.phone.includes(query)
);
if (filtered.length === 0) {
resultsDiv.innerHTML = '<div class="p-3 text-sm text-gray-500">No patients found</div>';
} else {
resultsDiv.innerHTML = filtered.map(patient => `
<div onclick="selectPatient('${patient.id}')" class="p-3 hover:bg-gray-50 cursor-pointer border-b border-gray-100 last:border-0 transition-colors">
<div class="flex items-center gap-3">
<img src="${patient.image}" class="w-8 h-8 rounded-full object-cover">
<div>
<p class="font-medium text-gray-900 text-sm">${patient.name}</p>
<p class="text-xs text-gray-500">${patient.id}${patient.phone}</p>
</div>
</div>
</div>
`).join('');
}
resultsDiv.classList.remove('hidden');
}
// Select patient from search
function selectPatient(patientId) {
const patient = mockPatients.find(p => p.id === patientId);
if (!patient) return;
currentPatient = patient;
document.getElementById('searchResults').classList.add('hidden');
document.getElementById('patientSearch').value = '';
// Show selected patient card
document.getElementById('spImage').src = patient.image;
document.getElementById('spName').textContent = patient.name;
document.getElementById('spId').textContent = patient.id;
document.getElementById('spPhone').textContent = patient.phone;
document.getElementById('selectedPatientCard').classList.remove('hidden');
}
// Clear selected patient
function clearPatient() {
currentPatient = null;
document.getElementById('selectedPatientCard').classList.add('hidden');
}
// Add line item to invoice
function addLineItem() {
const tbody = document.getElementById('lineItemsBody');
const row = document.createElement('tr');
row.className = 'line-item border-b border-gray-100 hover:bg-gray-50 transition-colors';
row.id = `lineItem-${lineItemCount}`;
row.innerHTML = `
<td class="py-3 pl-2">
<select class="w-full px-3 py-2 border border-gray-200 rounded-md text-sm focus-verify-teal outline-none bg-white" onchange="updateCalculation('${lineItemCount}')">
<option value="">Select Service</option>
<option value="consultation">Consultation</option>
<option value="lab_test">Laboratory Test</option>
<option value="xray">X-Ray</option>
<option value="surgery">Surgery</option>
<option value="medication">Medication</option>
<option value="therapy">Physical Therapy</option>
<option value="vaccination">Vaccination</option>
</select>
</td>
<td class="py-3">
<input type="text" class="w-full px-3 py-2 border border-gray-200 rounded-md text-sm focus-verify-teal outline-none" placeholder="Description">
</td>
<td class="py-3">
<input type="number" id="qty-${lineItemCount}" value="1" min="1" class="w-full px-3 py-2 border border-gray-200 rounded-md text-sm focus-verify-teal outline-none text-center" onchange="updateCalculation('${lineItemCount}')">
</td>
<td class="py-3">
<input type="number" id="rate-${lineItemCount}" value="0.00" min="0" step="0.01" class="w-full px-3 py-2 border border-gray-200 rounded-md text-sm focus-verify-teal outline-none" onchange="updateCalculation('${lineItemCount}')">
</td>
<td class="py-3">
<input type="text" id="amount-${lineItemCount}" value="$0.00" readonly class="w-full px-3 py-2 bg-gray-50 border border-gray-200 rounded-md text-sm text-gray-700 font-medium cursor-not-allowed font-variant-numeric">
</td>
<td class="py-3 text-right">
<button onclick="removeLineItem('${lineItemCount}')" class="text-gray-400 hover:text-red-500 transition-colors p-1 rounded hover:bg-red-50/50">
<i data-lucide="trash-2" class="w-4 h-4"></i>
</button>
</td>
`;
tbody.appendChild(row);
lineItemCount++;
// Refresh icons
lucide.createIcons();
}
// Remove line item
function removeLineItem(id) {
const row = document.getElementById(`lineItem-${id}`);
if (row) {
row.style.opacity = '0';
row.style.transform = 'translateX(-20px)';
setTimeout(() => {
row.remove();
calculateTotals();
}, 200);
}
}
// Update line item calculation
function updateCalculation(id) {
const qty = parseFloat(document.getElementById(`qty-${id}`).value) || 0;
const rate = parseFloat(document.getElementById(`rate-${id}`).value) || 0;
const amount = qty * rate;
document.getElementById(`amount-${id}`).value = '$' + amount.toFixed(2);
calculateTotals();
}
// Calculate invoice totals
function calculateTotals() {
let subtotal = 0;
// Sum all line items
for (let i = 0; i < lineItemCount; i++) {
const amountField = document.getElementById(`amount-${i}`);
if (amountField && amountField.parentElement.parentElement.style.display !== 'none') {
const value = parseFloat(amountField.value.replace('$', '')) || 0;
subtotal += value;
}
}
const tax = subtotal * 0.08; // 8% tax
const total = subtotal + tax;
// Update summary displays
document.getElementById('summarySubtotal').textContent = '$' + subtotal.toFixed(2);
document.getElementById('summaryTax').textContent = '$' + tax.toFixed(2);
document.getElementById('summaryTotal').textContent = '$' + total.toFixed(2);
document.getElementById('mobileTotal').textContent = '$' + total.toFixed(2);
}
// Save invoice
function saveInvoice() {
showToast('Invoice saved successfully!');
// In real app, would send to backend
}
// Print invoice
function printInvoice() {
window.print();
}
// Send invoice
function sendInvoice() {
showToast('Invoice sent to patient email!');
// In real app, would trigger email/SMS
}
// Toast notification
function showToast(message) {
const toast = document.getElementById('toast');
const toastMessage = document.getElementById('toastMessage');
toastMessage.textContent = message;
toast.classList.remove('translate-y-20', 'opacity-0');
setTimeout(() => {
toast.classList.add('translate-y-20', 'opacity-0');
}, 3000);
}
// Close search results when clicking outside
document.addEventListener('click', (e) => {
const searchContainer = document.getElementById('patientSearch');
const resultsDiv = document.getElementById('searchResults');
if (!searchContainer.contains(e.target) && !resultsDiv.contains(e.target)) {
resultsDiv.classList.add('hidden');
}
});
// Handle window resize
window.addEventListener('resize', () => {
if (window.innerWidth >= 1024) {
document.getElementById('mobileOverlay').classList.add('hidden');
document.body.style.overflow = '';
} else {
const sidebar = document.getElementById('sidebar');
sidebar.classList.add('-translate-x-full');
}
});