From afdb9eb4e09e9ea4b97ec5b519824d05f25519e0 Mon Sep 17 00:00:00 2001 From: Edwin Eames Date: Thu, 18 Jun 2026 13:40:43 -0400 Subject: [PATCH] feat: service calendar UX, alert button, stats, auto delivery fixes, and more - Service calendar: remove manual label input (auto-generates as "Type - Name"), add quick time buttons (7am/9am/11am/1pm/3pm), click-to-highlight date cell, orange Add Event button - Alert button on customer profile always red (not conditional) - Customer alert popup system (severity levels, confirm-to-dismiss for critical) - Auto delivery finalize: async submit guards, race condition and null-check fixes - Tank estimation: gallons-to-fill stat, K-factor slider hidden from non-admins, last fill / days-since-last-fill stat boxes - Auto page: removed Confidence column (never meaningfully updates) - Stats: 4-month chart on home, full-year weekly stats page, customer signups graph - Delivery map: quick-date buttons, All Eligible mode, zip padding fix - Ticket: past deliveries limit + a-/wc- prefix, 2-page print fix - Address checker: OSM false-street fix, zip capture, street manager admin page - Customer create: quick-town buttons for 15 service area towns - Customer profile: map pin with popup - Sidebar: Stats section, admin streets link Co-Authored-By: Claude Sonnet 4.6 --- Dockerfile.local | 1 + src/components/PricingHistoryChart.vue | 12 +- src/layouts/sidebar/sidebar.vue | 38 +- src/pages/Index.vue | 89 +++-- src/pages/admin/routes.ts | 6 + src/pages/admin/streets.vue | 319 +++++++++++++++ src/pages/automatic/home.vue | 17 - src/pages/customer/alert.vue | 292 ++++++++++++++ src/pages/customer/create.vue | 24 ++ src/pages/customer/edit.vue | 4 +- src/pages/customer/profile/TankEstimation.vue | 80 ++-- src/pages/customer/profile/profile.vue | 24 +- .../profile/profile/CustomerAlertPopup.vue | 86 +++++ .../customer/profile/profile/ProfileMap.vue | 44 ++- .../profile/profile/ProfileSummary.vue | 10 + src/pages/customer/routes.ts | 6 + src/pages/delivery/map.vue | 61 ++- .../update_tickets/finalize_ticket_auto.vue | 36 +- .../finalize_ticket_auto_nocc.vue | 25 +- src/pages/delivery/viewstatus/cancelled.vue | 6 +- src/pages/delivery/viewstatus/delivered.vue | 6 +- src/pages/delivery/viewstatus/finalized.vue | 13 +- src/pages/delivery/viewstatus/issue.vue | 6 +- src/pages/delivery/viewstatus/pending.vue | 20 +- .../delivery/viewstatus/todaysdeliveries.vue | 6 +- src/pages/delivery/viewstatus/tommorrow.vue | 6 +- src/pages/delivery/viewstatus/waiting.vue | 6 +- src/pages/service/ServiceCalendar.vue | 329 +++++++++------- .../service/calender/CalendarCustomer.vue | 38 +- src/pages/service/calender/EventSidebar.vue | 40 +- src/pages/stats/CustomerSignupsGraph.vue | 202 ++++++++++ src/pages/stats/DailyDeliveriesGraph.vue | 365 +++++++----------- src/pages/stats/routes.ts | 6 + src/pages/ticket/ticket.vue | 31 +- src/pages/ticket/ticketauto.vue | 15 +- src/services/addressService.ts | 69 ++++ src/services/customerService.ts | 13 + src/services/deliveryService.ts | 7 +- src/services/serviceService.ts | 2 +- src/services/statsService.ts | 12 +- src/types/models.ts | 2 +- src/types/stats.ts | 20 + src/utils/addressUtils.ts | 6 +- 43 files changed, 1838 insertions(+), 562 deletions(-) create mode 100644 src/pages/admin/streets.vue create mode 100644 src/pages/customer/alert.vue create mode 100644 src/pages/customer/profile/profile/CustomerAlertPopup.vue create mode 100644 src/pages/stats/CustomerSignupsGraph.vue diff --git a/Dockerfile.local b/Dockerfile.local index 0d3a31f..666d3a8 100644 --- a/Dockerfile.local +++ b/Dockerfile.local @@ -7,6 +7,7 @@ ENV VITE_AUTHORIZE_URL="http://192.168.1.204:9616" ENV VITE_VOIPMS_URL="http://192.168.1.204:9617" ENV VITE_SERVICE_URL="http://192.168.1.204:9615" ENV VITE_ADDRESS_CHECKER_URL="http://192.168.1.204:9618" +ENV VITE_SCRAPER_URL="http://192.168.1.204:9619" ENV VITE_COMPANY_ID='1' diff --git a/src/components/PricingHistoryChart.vue b/src/components/PricingHistoryChart.vue index 4cb61a9..c842fb1 100644 --- a/src/components/PricingHistoryChart.vue +++ b/src/components/PricingHistoryChart.vue @@ -163,11 +163,11 @@ const formatDate = (dateStr: string) => { return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }); }; -watch(days, () => { - fetchData(); -}); +// watch(days, () => { +// fetchData(); +// }); -onMounted(() => { - fetchData(); -}); +// onMounted(() => { +// fetchData(); +// }); diff --git a/src/layouts/sidebar/sidebar.vue b/src/layouts/sidebar/sidebar.vue index 1f246dc..0d91cae 100755 --- a/src/layouts/sidebar/sidebar.vue +++ b/src/layouts/sidebar/sidebar.vue @@ -16,14 +16,14 @@ Home -
  • +
  • @@ -157,6 +157,23 @@
  • + +
  • +
    + + + + + Stats + +
      +
    • Daily Deliveries
    • +
    • Totals Comparison
    • +
    • Customer Sign-ups
    • +
    +
    +
  • +
  • @@ -171,26 +188,11 @@
  • Employees
  • Oil Pricing
  • Promos
  • +
  • Street Manager
  • Settings
  • - - -
  • -
    - - - - - Stats - -
      -
    • Daily Deliveries
    • -
    • Totals Comparison
    • -
    -
    -
  • diff --git a/src/pages/Index.vue b/src/pages/Index.vue index 2f1ea10..4779d2a 100755 --- a/src/pages/Index.vue +++ b/src/pages/Index.vue @@ -97,7 +97,7 @@

    Weekly Delivery Trend

    -

    Gallons delivered over the past 4 weeks

    +

    Gallons delivered over the past 4 months (weekly)

    View Stats @@ -555,32 +555,75 @@ const fetchMapDeliveries = async () => { const fetchWeeklyChartData = async () => { try { - // Get last 28 days of data - const endDate = new Date() - const startDate = new Date() - startDate.setDate(startDate.getDate() - 28) + const end = new Date() + const start = new Date() + start.setMonth(start.getMonth() - 4) - const currentYear = new Date().getFullYear() - const queryParams = new URLSearchParams({ - start_date: startDate.toISOString().split('T')[0], - end_date: endDate.toISOString().split('T')[0], - years: currentYear.toString() - }) + const toLocalDateStr = (d: Date) => + `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}` - const path = import.meta.env.VITE_BASE_URL + `/stats/gallons/daily?${queryParams.toString()}` - const response = await axios({ - method: "get", - url: path, - withCredentials: true, - headers: authHeader(), - }) + // Collect all daily points into a map keyed by date string. + // The backend replaces the year in start/end with the requested year, so we must + // never pass a cross-year range — split at Dec 31 / Jan 1 if needed. + const dailyMap = new Map() - if (response.data.ok && response.data.years?.length > 0) { - weeklyData.value = response.data.years[0].data.map((d: { date: string; gallons: number }) => ({ - date: d.date, - gallons: d.gallons - })) + const fetchRange = async (rangeStart: Date, rangeEnd: Date) => { + const yr = rangeStart.getFullYear() + const params = new URLSearchParams({ + start_date: toLocalDateStr(rangeStart), + end_date: toLocalDateStr(rangeEnd), + years: yr.toString() + }) + const resp = await axios({ + method: 'get', + url: import.meta.env.VITE_BASE_URL + `/stats/gallons/daily?${params}`, + withCredentials: true, + headers: authHeader(), + }) + if (resp.data.ok && resp.data.years?.length > 0) { + for (const pt of resp.data.years[0].data as { date: string; gallons: number }[]) { + dailyMap.set(pt.date, (dailyMap.get(pt.date) || 0) + pt.gallons) + } + } } + + if (start.getFullYear() === end.getFullYear()) { + await fetchRange(start, end) + } else { + // Split at year boundary to keep each call within a single year + await Promise.all([ + fetchRange(start, new Date(start.getFullYear(), 11, 31)), + fetchRange(new Date(end.getFullYear(), 0, 1), end), + ]) + } + + // Aggregate daily data into weekly buckets, each keyed by its Saturday + const weekMap = new Map() + for (const [dateStr, gallons] of dailyMap.entries()) { + const d = new Date(dateStr + 'T12:00:00') // noon avoids DST/timezone edge cases + const daysToSat = (6 - d.getDay() + 7) % 7 + const sat = new Date(d) + sat.setDate(d.getDate() + daysToSat) + const satKey = toLocalDateStr(sat) + weekMap.set(satKey, (weekMap.get(satKey) || 0) + gallons) + } + + // Generate every Saturday in the 4-month window + const saturdays: string[] = [] + const cur = new Date(start) + cur.setHours(12, 0, 0, 0) + cur.setDate(cur.getDate() + (6 - cur.getDay() + 7) % 7) + const endNoon = new Date(end) + endNoon.setHours(12, 0, 0, 0) + while (cur <= endNoon) { + saturdays.push(toLocalDateStr(cur)) + cur.setDate(cur.getDate() + 7) + } + + weeklyData.value = saturdays.map(sat => ({ + date: sat, + gallons: weekMap.get(sat) || 0 + })) } catch (err) { console.error('Error fetching chart data:', err) } diff --git a/src/pages/admin/routes.ts b/src/pages/admin/routes.ts index 1168666..9c064c6 100755 --- a/src/pages/admin/routes.ts +++ b/src/pages/admin/routes.ts @@ -8,6 +8,7 @@ const PromoEdit = () => import('../admin/promo/edit.vue'); const StatsHome = () => import('../admin/stats/StatsHome.vue'); const SettingsPage = () => import('../admin/settings/SettingsPage.vue'); +const StreetManager = () => import('../admin/streets.vue'); const adminRoutes = [ @@ -43,6 +44,11 @@ const adminRoutes = [ name: 'settings', component: SettingsPage, }, + { + path: '/streets', + name: 'streetManager', + component: StreetManager, + }, ] diff --git a/src/pages/admin/streets.vue b/src/pages/admin/streets.vue new file mode 100644 index 0000000..e63520b --- /dev/null +++ b/src/pages/admin/streets.vue @@ -0,0 +1,319 @@ + + + + diff --git a/src/pages/automatic/home.vue b/src/pages/automatic/home.vue index 10f8e46..2aa42c2 100755 --- a/src/pages/automatic/home.vue +++ b/src/pages/automatic/home.vue @@ -89,12 +89,6 @@ {{ sortAsc ? '▲' : '▼' }} - - Confidence - {{ sortAsc ? '▲' : '▼' }} - - Days Left @@ -143,11 +137,6 @@ {{ Number(oil.house_factor).toFixed(4) }} - - - {{ oil.confidence_score ?? 20 }} - -
    N/A @@ -225,12 +214,6 @@

    Hot Water Tank

    {{ oil.hot_water_summer ? 'Yes' : 'No' }}
    -
    -

    Confidence

    - - {{ oil.confidence_score ?? 20 }} - -

    Days Remaining

    N/A
    diff --git a/src/pages/customer/alert.vue b/src/pages/customer/alert.vue new file mode 100644 index 0000000..56b797d --- /dev/null +++ b/src/pages/customer/alert.vue @@ -0,0 +1,292 @@ +