Files
eamco_office_frontend/src/pages/ticket/ticket.vue
Edwin Eames 1a53e50d91 feat: 5-tier pricing UI, market ticker, delivery map, and stats dashboard
Full frontend companion to the API updates:

- Pricing: Oil price admin page now supports 5-tier configuration for
  same-day/prime/emergency fees with collapsible tier sections
- Market Ticker: Add GlobalMarketTicker and OilPriceTicker components
  with real-time commodity + competitor prices in header bar
- Delivery Map: New interactive Leaflet map view for daily deliveries
- Stats: Add PricingHistoryChart component and info pages for market
  trends with daily/weekly/monthly gallon charts and YoY comparisons
- Layout: Refactor header navbar to separate search into navbar-center,
  add oilPrice Pinia store with polling, update sidebar navigation
- Forms: Wire tier selection into delivery create/edit flows, update
  types and services for new pricing and scraper API endpoints

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 17:54:30 -05:00

283 lines
9.5 KiB
Vue

<!-- src/pages/ticket/ticket.vue -->
<template>
<div class=" max-w-5xl text-black bg-white font-mono text-md">
<div class="grid grid-cols-12 pt-10">
<div class="col-span-6">
<div class="grid grid-cols-12">
<div class="col-span-2 pt-2 pl-4">#2 </div>
<div class="col-span-2 pt-2"></div>
<div class="col-span-2 pt-2"></div>
<div class="col-span-2 pt-2 ">{{ customer_tank.tank_size }}</div>
<div class="col-span-1 pt-2 ">{{ customer_description.fill_location }}</div>
<div class="col-span-3 text-xs pt-3 ">{{ customer.customer_phone_number }}</div>
</div>
<div class="grid grid-cols-12 pt-2 pb-2">
<div class="col-span-9 pl-5">
{{ customer.customer_first_name }} {{ customer.customer_last_name }}
</div>
<div class="col-span-3 ">{{ customer.account_number }}</div>
<div class="col-span-12 pl-5">{{ customer.customer_address }}</div>
<div class="col-span-12 pl-5">{{ customer.customer_apt }}</div>
<div class="col-span-8 pl-5">
<div class="grid grid-cols-12">
<div class="col-span-5"> {{ customer.customer_town }}</div>
<div class="col-span-3">
<div v-if="customer.customer_state == 0">Ma</div>
</div>
<div class="col-span-4"> {{ customer.customer_zip }}</div>
</div>
</div>
</div>
<div class="grid grid-cols-12 pl-6 pb-6 gap-10 max-h-32">
<div class="col-span-6">
<div class="grid grid-cols-12">
<div class="col-span-12 ">{{ customer_description.description }}</div>
<div class="col-span-12 "></div>
<div class="col-span-12 text-lg">Credit Card</div>
<div class="col-span-12" v-if="promo">{{ promo_text }}</div>
</div>
</div>
<div class="col-span-6 border-2" v-if="delivery.dispatcher_notes">
<div class="grid grid-cols-12">
<div class="col-span-12 p-2">{{ delivery.dispatcher_notes }}</div>
</div>
</div>
</div>
<div class="grid grid-cols-12">
<div class="col-span-6 ">
<div class="col-span-12 pl-5">Auburn Oil</div>
<div class="col-span-12 pl-5">PO BOX 174</div>
<div class="col-span-8 pl-5">
<div class="grid grid-cols-12">
<div class="col-span-5"> Auburn</div>
<div class="col-span-3">
Ma
</div>
<div class="col-span-4">01501 </div>
</div>
</div>
<div class="col-span-12 pl-5">508 426 8800</div>
</div>
<div class="col-span-6 ">
<div v-if="past_deliveries.length > 0">
<div class="col-span-6" v-for="past_delivery in past_deliveries"
:key="past_delivery.fill_date">
{{ past_delivery.fill_date }} - {{ past_delivery.gallons_delivered }}
</div>
</div>
<div v-else>
<div class="col-span-6 text-center">Have a Great day :)</div>
</div>
</div>
</div>
</div>
<div class="col-span-6 ">
<div class="col-span-4 ">
<div class="grid grid-cols-12 ">
<div class="col-span-12 h-7 pl-4 pt-2"></div>
<div class="col-span-12 h-7 pl-4 pt-2"></div>
<div class="col-span-12 h-7 pl-4 pt-2"></div>
<div class="col-span-12 h-7 pl-4 pt-2"></div>
<div class="col-span-12 h-7 pl-4 pt-4"> </div>
<div class="col-span-12 h-7 pt-6"></div>
<div class="col-span-12 h-7"></div>
<div class="col-span-12 h-7 pl-8"></div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { notify } from "@kyvg/vue3-notification"
import { deliveryService } from '../../services/deliveryService'
import { customerService } from '../../services/customerService'
import { adminService } from '../../services/adminService'
import { queryService } from '../../services/queryService'
interface PastDelivery {
gallons_delivered: number
fill_date: string
}
const route = useRoute()
// State
const loaded = ref(false)
const past_deliveries = ref<PastDelivery[]>([])
const delivery = ref<any>({
id: '',
customer_id: 0,
customer_name: '',
customer_address: '',
customer_town: '',
customer_state: 0,
customer_zip: '',
gallons_ordered: 0,
customer_asked_for_fill: 0,
gallons_delivered: '',
customer_filled: 0,
delivery_status: 0,
when_ordered: '',
when_delivered: '',
expected_delivery_date: '',
automatic: 0,
oil_id: 0,
supplier_price: '',
customer_price: 0,
customer_temperature: '',
dispatcher_notes: '',
prime: 0,
same_day: 0,
emergency: 0,
payment_type: 0,
payment_card_id: 0,
driver_employee_id: 0,
driver_first_name: '',
driver_last_name: '',
promo_id: 0,
})
const customer_tank = ref<any>({
id: 0,
last_tank_inspection: null,
tank_status: false,
outside_or_inside: false,
tank_size: 0,
})
const customer = ref<any>({
id: 0,
user_id: 0,
customer_first_name: '',
customer_last_name: '',
customer_town: '',
customer_address: '',
customer_state: 0,
customer_zip: '',
customer_apt: '',
customer_home_type: 0,
customer_phone_number: '',
account_number: '',
})
const customer_description = ref<any>({
id: 0,
customer_id: 0,
account_number: '',
company_id: '',
fill_location: 0,
description: '',
})
const promo = ref<any>(null)
const promo_text = ref('')
const todays_price = ref(0)
// Methods
async function getOrder(deliveryId: string | number) {
try {
const response = await deliveryService.getById(Number(deliveryId))
delivery.value = (response.data as any)?.delivery || response.data
getCustomer(delivery.value.customer_id)
if (delivery.value.promo_id) {
getPromo(delivery.value.promo_id)
}
} catch (error) {
notify({
title: "Error",
text: "Could not get delivery",
type: "error",
})
}
}
async function getCustomer(userId: number) {
try {
const response = await customerService.getById(userId)
customer.value = (response.data as any)?.customer || response.data
getPastDeliveries(customer.value.id)
getCustomerDescription(customer.value.id)
getCustomerTank(customer.value.id)
} catch (error) {
console.error('Failed to fetch customer:', error)
}
}
async function getCustomerTank(userId: number) {
try {
const response = await customerService.getTank(userId)
customer_tank.value = (response.data as any)?.tank || response.data
} catch (error) {
console.error('Failed to fetch tank:', error)
}
}
async function getCustomerDescription(userId: number) {
try {
const response = await customerService.getDescription(userId)
customer_description.value = (response.data as any)?.description || response.data
} catch (error) {
console.error('Failed to fetch description:', error)
}
}
async function getPromo(promoId: number) {
try {
const response = await adminService.promos.getById(promoId)
promo.value = response.data
promo_text.value = promo.value?.text_on_ticket || ''
} catch (error) {
console.error('Failed to fetch promo:', error)
}
}
async function getTodayPrice() {
try {
const response = await queryService.getOilPrice()
if ((response.data as any).ok) {
todays_price.value = (response.data as any).price_for_customer
}
} catch (error) {
notify({
title: "Error",
text: "Could not get oil pricing",
type: "error",
})
}
}
async function getPastDeliveries(userId: number) {
try {
const response = await deliveryService.getByCustomer(userId)
past_deliveries.value = (response.data as any)?.deliveries || response.data || []
} catch (error) {
console.error('Failed to fetch past deliveries:', error)
}
}
// Watchers
watch(() => route.params.id, (newId) => {
if (newId) {
getOrder(newId as string)
getTodayPrice()
}
})
// Lifecycle
onMounted(() => {
if (route.params.id) {
getOrder(route.params.id as string)
getTodayPrice()
}
})
</script>