From 1b40187ade5d0e0c88e66c202cd7498cf9cc2ee5 Mon Sep 17 00:00:00 2001 From: odzugkoev Date: Tue, 10 Mar 2026 20:36:25 -0400 Subject: [PATCH] date drop added --- database.js | 39 ++++++++---- package.json | 2 +- public/css/style.css | 94 +++++++++++++++++++++++++++ public/js/dashboard.js | 141 +++++++++++++++++++++++++++++++++++++++-- routes/orders.js | 35 ++++++++-- views/dashboard.ejs | 18 +++++- 6 files changed, 305 insertions(+), 24 deletions(-) diff --git a/database.js b/database.js index 76b5672..d8ae169 100644 --- a/database.js +++ b/database.js @@ -301,8 +301,16 @@ class DatabaseManager { params.push(filters.status); } - if (filters.date) { - // Get orders from start of day to end of day + if (filters.startDate && filters.endDate) { + query += ' AND created_at BETWEEN ? AND ?'; + params.push(filters.startDate, filters.endDate); + } else if (filters.startDate) { + query += ' AND created_at >= ?'; + params.push(filters.startDate); + } else if (filters.endDate) { + query += ' AND created_at <= ?'; + params.push(filters.endDate); + } else if (filters.date) { const startOfDay = Math.floor(new Date(filters.date).setHours(0, 0, 0, 0) / 1000); const endOfDay = Math.floor(new Date(filters.date).setHours(23, 59, 59, 999) / 1000); query += ' AND created_at BETWEEN ? AND ?'; @@ -333,9 +341,7 @@ class DatabaseManager { })); } - getOrderStats() { - const today = Math.floor(new Date().setHours(0, 0, 0, 0) / 1000); - + getOrderStats(startDate = null, endDate = null) { const stats = { total: 0, new: 0, @@ -343,12 +349,23 @@ class DatabaseManager { ready: 0 }; - const rows = this.db.prepare(` - SELECT local_status, COUNT(*) as count - FROM orders - WHERE created_at >= ? - GROUP BY local_status - `).all(today); + let query, params; + if (startDate != null && endDate != null) { + query = 'SELECT local_status, COUNT(*) as count FROM orders WHERE created_at BETWEEN ? AND ? GROUP BY local_status'; + params = [startDate, endDate]; + } else if (startDate != null) { + query = 'SELECT local_status, COUNT(*) as count FROM orders WHERE created_at >= ? GROUP BY local_status'; + params = [startDate]; + } else if (endDate != null) { + query = 'SELECT local_status, COUNT(*) as count FROM orders WHERE created_at <= ? GROUP BY local_status'; + params = [endDate]; + } else { + const today = Math.floor(new Date().setHours(0, 0, 0, 0) / 1000); + query = 'SELECT local_status, COUNT(*) as count FROM orders WHERE created_at >= ? GROUP BY local_status'; + params = [today]; + } + + const rows = this.db.prepare(query).all(...params); for (const row of rows) { if (row.local_status === 'new') stats.new = row.count; diff --git a/package.json b/package.json index 600c768..ff20201 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kitchen-agent", - "version": "1.0.6", + "version": "1.0.7", "description": "Kitchen Agent for ThinkLink Food Order Management", "main": "server.js", "scripts": { diff --git a/public/css/style.css b/public/css/style.css index 6d39229..0cfcc09 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -420,6 +420,70 @@ button, a, input, select, textarea { color: white; } +/* Date Filter */ +.date-filter-group { + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; +} + +.date-filter-select { + padding: 10px 14px; + border: 2px solid #ddd; + border-radius: 4px; + font-size: 15px; + font-weight: 500; + color: #333; + background-color: #fff; + cursor: pointer; + min-height: 48px; + min-width: 160px; + transition: border-color 0.3s; + appearance: auto; +} + +.date-filter-select:focus { + outline: none; + border-color: #667eea; +} + +.date-filter-custom { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} + +.date-filter-input { + padding: 9px 12px; + border: 2px solid #ddd; + border-radius: 4px; + font-size: 14px; + color: #333; + background-color: #fff; + min-height: 44px; + transition: border-color 0.3s; +} + +.date-filter-input:focus { + outline: none; + border-color: #667eea; +} + +.date-filter-separator { + font-size: 14px; + color: #666; + font-weight: 500; +} + +.date-filter-apply { + padding: 9px 18px !important; + font-size: 14px !important; + min-height: 44px !important; + white-space: nowrap; +} + /* Sync Button with Integrated Loading State */ .sync-button { position: relative; @@ -1047,6 +1111,14 @@ button, a, input, select, textarea { justify-content: center; } + .date-filter-group { + justify-content: center; + } + + .date-filter-select { + min-width: 140px; + } + .tabs { flex-direction: column; } @@ -1074,6 +1146,28 @@ button, a, input, select, textarea { width: 100%; } + .date-filter-group { + flex-direction: column; + align-items: stretch; + } + + .date-filter-select { + width: 100%; + } + + .date-filter-custom { + flex-direction: column; + align-items: stretch; + } + + .date-filter-input { + width: 100%; + } + + .date-filter-separator { + text-align: center; + } + .status-modal-content { padding: 25px 20px; } diff --git a/public/js/dashboard.js b/public/js/dashboard.js index 9e02681..ea2887d 100644 --- a/public/js/dashboard.js +++ b/public/js/dashboard.js @@ -1,6 +1,9 @@ // Dashboard functionality let currentFilter = 'all'; +let currentDateFilter = 'today'; +let customDateStart = ''; +let customDateEnd = ''; let currentOrderIdForCancel = null; let pendingStatusChange = { orderId: null, @@ -118,6 +121,7 @@ function refreshAbandonedCallCount() { // Initialize dashboard document.addEventListener('DOMContentLoaded', function() { setupFilterButtons(); + setupDateFilter(); audioNotification.init(); refreshOrders(); refreshAbandonedCallCount(); @@ -136,19 +140,135 @@ function setupFilterButtons() { filterButtons.forEach(button => { button.addEventListener('click', function() { - // Remove active class from all buttons filterButtons.forEach(btn => btn.classList.remove('active')); - - // Add active class to clicked button this.classList.add('active'); - - // Update filter and refresh currentFilter = this.dataset.filter; refreshOrders(); }); }); } +function setupDateFilter() { + const select = document.getElementById('dateFilterSelect'); + const customPanel = document.getElementById('dateFilterCustom'); + const applyBtn = document.getElementById('dateFilterApply'); + const fromInput = document.getElementById('dateFilterFrom'); + const toInput = document.getElementById('dateFilterTo'); + + if (!select || !customPanel || !applyBtn || !fromInput || !toInput) return; + + // Set default dates on the pickers to today + const todayStr = formatDateForInput(new Date()); + fromInput.value = todayStr; + toInput.value = todayStr; + + select.addEventListener('change', function() { + const val = this.value; + currentDateFilter = val; + + if (val === 'custom') { + customPanel.style.display = 'flex'; + // Entering custom mode should immediately reflect the currently selected dates + // (defaults to today on first use). Further changes require pressing Apply. + if (fromInput.value && toInput.value) { + customDateStart = fromInput.value; + customDateEnd = toInput.value; + } + refreshOrders(); + } else { + customPanel.style.display = 'none'; + refreshOrders(); + } + + updateStatsTotalLabel(); + }); + + applyBtn.addEventListener('click', function() { + if (!fromInput.value || !toInput.value) { + showToast('Please select both start and end dates', 'error'); + return; + } + if (fromInput.value > toInput.value) { + showToast('Start date must be before end date', 'error'); + return; + } + customDateStart = fromInput.value; + customDateEnd = toInput.value; + refreshOrders(); + updateStatsTotalLabel(); + }); + + // Allow pressing Enter in date inputs to apply + fromInput.addEventListener('keydown', function(e) { if (e.key === 'Enter') applyBtn.click(); }); + toInput.addEventListener('keydown', function(e) { if (e.key === 'Enter') applyBtn.click(); }); +} + +function getDateRangeParams() { + const now = new Date(); + + switch (currentDateFilter) { + case 'today': { + const start = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0); + const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999); + return { startDate: Math.floor(start.getTime() / 1000), endDate: Math.floor(end.getTime() / 1000) }; + } + case 'yesterday': { + const yd = new Date(now); + yd.setDate(yd.getDate() - 1); + const start = new Date(yd.getFullYear(), yd.getMonth(), yd.getDate(), 0, 0, 0); + const end = new Date(yd.getFullYear(), yd.getMonth(), yd.getDate(), 23, 59, 59, 999); + return { startDate: Math.floor(start.getTime() / 1000), endDate: Math.floor(end.getTime() / 1000) }; + } + case 'last7': { + const start = new Date(now); + start.setDate(start.getDate() - 6); + start.setHours(0, 0, 0, 0); + const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999); + return { startDate: Math.floor(start.getTime() / 1000), endDate: Math.floor(end.getTime() / 1000) }; + } + case 'last30': { + const start = new Date(now); + start.setDate(start.getDate() - 29); + start.setHours(0, 0, 0, 0); + const end = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 59, 59, 999); + return { startDate: Math.floor(start.getTime() / 1000), endDate: Math.floor(end.getTime() / 1000) }; + } + case 'all': + // Use a minimal unix start date to represent "all time" without relying on server defaults. + return { startDate: 1 }; + case 'custom': { + if (!customDateStart || !customDateEnd) return {}; + const start = new Date(customDateStart + 'T00:00:00'); + const end = new Date(customDateEnd + 'T23:59:59'); + return { startDate: Math.floor(start.getTime() / 1000), endDate: Math.floor(end.getTime() / 1000) }; + } + default: + return {}; + } +} + +function formatDateForInput(date) { + const y = date.getFullYear(); + const m = String(date.getMonth() + 1).padStart(2, '0'); + const d = String(date.getDate()).padStart(2, '0'); + return y + '-' + m + '-' + d; +} + +function updateStatsTotalLabel() { + const label = document.getElementById('stat-total-label'); + if (!label) return; + + const labels = { + today: 'Total Today', + yesterday: 'Total Yesterday', + last7: 'Total (7 Days)', + last30: 'Total (30 Days)', + all: 'Total (All Time)', + custom: 'Total (Custom)' + }; + label.textContent = labels[currentDateFilter] || 'Total'; +} + function refreshOrders() { const syncButton = document.getElementById('syncButton'); const syncText = syncButton ? syncButton.querySelector('.sync-text') : null; @@ -161,8 +281,15 @@ function refreshOrders() { } } - const statusParam = currentFilter === 'all' ? '' : currentFilter; - const url = '/api/orders' + (statusParam ? '?status=' + statusParam : ''); + const params = new URLSearchParams(); + if (currentFilter !== 'all') { + params.set('status', currentFilter); + } + const dateRange = getDateRangeParams(); + if (dateRange.startDate) params.set('startDate', dateRange.startDate); + if (dateRange.endDate) params.set('endDate', dateRange.endDate); + const qs = params.toString(); + const url = '/api/orders' + (qs ? '?' + qs : ''); fetch(url) .then(response => response.json()) diff --git a/routes/orders.js b/routes/orders.js index 6a6b739..cfc547b 100644 --- a/routes/orders.js +++ b/routes/orders.js @@ -36,12 +36,39 @@ async function ordersRoutes(fastify, options) { limit: parseInt(req.query.limit, 10) || 50 }; - // Get today's date for stats - const today = new Date(); - filters.date = today; + const normalizeUnixSeconds = (v) => { + const n = Number(v); + if (!Number.isFinite(n) || n <= 0) return 0; + // Accept both seconds and milliseconds. + return n > 1e12 ? Math.floor(n / 1000) : Math.floor(n); + }; + + let startDate = normalizeUnixSeconds(req.query.startDate); + let endDate = normalizeUnixSeconds(req.query.endDate); + + // Be resilient to inverted ranges + if (startDate > 0 && endDate > 0 && startDate > endDate) { + const tmp = startDate; + startDate = endDate; + endDate = tmp; + } + + if (startDate > 0 && endDate > 0) { + filters.startDate = startDate; + filters.endDate = endDate; + } else if (startDate > 0) { + filters.startDate = startDate; + } else if (endDate > 0) { + filters.endDate = endDate; + } else { + filters.date = new Date(); + } const orders = database.getOrders(filters); - const stats = database.getOrderStats(); + const stats = database.getOrderStats( + filters.startDate || null, + filters.endDate || null + ); return { error: false, orders, stats }; }); diff --git a/views/dashboard.ejs b/views/dashboard.ejs index 9508a13..7e47a7b 100644 --- a/views/dashboard.ejs +++ b/views/dashboard.ejs @@ -37,7 +37,7 @@ <% if (showStats) { %>
-
Total Today
+
Total Today
<%= stats.total %>
@@ -62,6 +62,22 @@
+
+ + +