sashank1989
feat: KDS side pane split by Dining/Takeaway/Delivery with source badges on cards
f8de0a1 | // Utility helpers for the frontend | |
| /** | |
| * Format currency in INR | |
| */ | |
| export function formatCurrency(amount) { | |
| if (amount == null) return '₹0.00'; | |
| return new Intl.NumberFormat('en-IN', { | |
| style: 'currency', | |
| currency: 'INR', | |
| minimumFractionDigits: 2 | |
| }).format(amount); | |
| } | |
| /** | |
| * Format date/time | |
| */ | |
| export function formatDate(dateStr, options = {}) { | |
| if (!dateStr) return '-'; | |
| const date = new Date(dateStr); | |
| return new Intl.DateTimeFormat('en-IN', { | |
| dateStyle: 'medium', | |
| timeStyle: 'short', | |
| ...options | |
| }).format(date); | |
| } | |
| /** | |
| * Format time only | |
| */ | |
| export function formatTime(dateStr) { | |
| if (!dateStr) return '-'; | |
| return new Intl.DateTimeFormat('en-IN', { timeStyle: 'short' }).format(new Date(dateStr)); | |
| } | |
| /** | |
| * Time elapsed since a date (e.g., "5 min ago") | |
| */ | |
| export function timeAgo(dateStr) { | |
| if (!dateStr) return ''; | |
| const now = Date.now(); | |
| const then = new Date(dateStr).getTime(); | |
| const diff = Math.floor((now - then) / 1000); | |
| if (diff < 60) return `${diff}s`; | |
| if (diff < 3600) return `${Math.floor(diff / 60)}m`; | |
| if (diff < 86400) return `${Math.floor(diff / 3600)}h`; | |
| return `${Math.floor(diff / 86400)}d`; | |
| } | |
| /** | |
| * Timer display (mm:ss) from a start date | |
| */ | |
| export function elapsedTimer(dateStr) { | |
| if (!dateStr) return '00:00'; | |
| const diff = Math.floor((Date.now() - new Date(dateStr).getTime()) / 1000); | |
| const mins = Math.floor(diff / 60); | |
| const secs = diff % 60; | |
| return `${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`; | |
| } | |
| /** | |
| * Debounce function | |
| */ | |
| export function debounce(fn, delay = 300) { | |
| let timer; | |
| return (...args) => { | |
| clearTimeout(timer); | |
| timer = setTimeout(() => fn(...args), delay); | |
| }; | |
| } | |
| /** | |
| * Generate a short ID for local use | |
| */ | |
| export function shortId() { | |
| return Math.random().toString(36).substring(2, 9); | |
| } | |
| /** | |
| * Capitalize first letter | |
| */ | |
| export function capitalize(str) { | |
| if (!str) return ''; | |
| return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); | |
| } | |
| /** | |
| * Truncate string | |
| */ | |
| export function truncate(str, len = 30) { | |
| if (!str || str.length <= len) return str || ''; | |
| return str.substring(0, len) + '...'; | |
| } | |
| /** | |
| * Get order source display label | |
| */ | |
| export function orderSourceLabel(source) { | |
| const labels = { | |
| dine_in: 'Dine In', | |
| take_away: 'Takeaway', | |
| takeaway: 'Takeaway', | |
| delivery: 'Delivery', | |
| website: 'Website', | |
| whatsapp: 'WhatsApp', | |
| swiggy: 'Swiggy', | |
| zomato: 'Zomato', | |
| qr_table: 'QR Order', | |
| qr: 'QR Order' | |
| }; | |
| return labels[source] || capitalize(source); | |
| } | |
| /** | |
| * Get status color class | |
| */ | |
| export function statusColor(status) { | |
| const colors = { | |
| pending: 'badge-warning', | |
| preparing: 'badge-info', | |
| ready: 'badge-success', | |
| served: 'badge-success', | |
| completed: 'badge-success', | |
| cancelled: 'badge-danger', | |
| voided: 'badge-danger', | |
| available: 'badge-success', | |
| occupied: 'badge-danger', | |
| reserved: 'badge-warning', | |
| cleaning: 'badge-info' | |
| }; | |
| return colors[status] || 'badge-info'; | |
| } | |
| /** | |
| * Kitchen station label | |
| */ | |
| export function stationLabel(station) { | |
| const labels = { | |
| south_indian: 'South Indian', | |
| chinese: 'Chinese', | |
| tandoor: 'Tandoor', | |
| juice: 'Juice Bar', | |
| bakery: 'Bakery', | |
| main: 'Main Kitchen', | |
| bar: 'Bar' | |
| }; | |
| return labels[station] || capitalize(station); | |
| } | |
| /** | |
| * Play notification sound | |
| */ | |
| export function playSound(type = 'notify') { | |
| try { | |
| const ctx = new (window.AudioContext || window.webkitAudioContext)(); | |
| const osc = ctx.createOscillator(); | |
| const gain = ctx.createGain(); | |
| osc.connect(gain); | |
| gain.connect(ctx.destination); | |
| if (type === 'order') { | |
| osc.frequency.value = 880; | |
| gain.gain.value = 0.3; | |
| osc.start(); | |
| osc.stop(ctx.currentTime + 0.15); | |
| setTimeout(() => { | |
| const osc2 = ctx.createOscillator(); | |
| osc2.connect(gain); | |
| osc2.frequency.value = 1100; | |
| osc2.start(); | |
| osc2.stop(ctx.currentTime + 0.15); | |
| }, 180); | |
| } else { | |
| osc.frequency.value = 660; | |
| gain.gain.value = 0.2; | |
| osc.start(); | |
| osc.stop(ctx.currentTime + 0.1); | |
| } | |
| } catch { | |
| // Audio not available | |
| } | |
| } | |
| /** | |
| * Check if user has a specific role | |
| */ | |
| export function hasRole(user, ...roles) { | |
| if (!user?.role?.name) return false; | |
| return roles.includes(user.role.name.toLowerCase()); | |
| } | |
| /** | |
| * Calculate GST breakdown | |
| */ | |
| export function calculateGST(amount, gstRate = 5) { | |
| const gstAmount = (amount * gstRate) / 100; | |
| return { | |
| base: amount, | |
| cgst: gstAmount / 2, | |
| sgst: gstAmount / 2, | |
| total: amount + gstAmount, | |
| rate: gstRate | |
| }; | |
| } | |