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>
This commit is contained in:
2026-02-08 17:54:30 -05:00
parent 6c28c0c2d2
commit 1a53e50d91
69 changed files with 4756 additions and 3040 deletions

View File

@@ -1,9 +1,9 @@
<!-- src/pages/customer/profile/profile.vue -->
<template>
<div class="w-full min-h-screen bg-base-200 px-4 md:px-10">
<div class="w-full min-h-screen px-4 md:px-10">
<!-- ... breadcrumbs ... -->
<div v-if="customer && customer.id" class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
<div v-if="customer && customer.id" class="mt-6">
<!-- Current Plan Status Banner - Same as ServicePlanEdit.vue -->
<div v-if="servicePlan && servicePlan.contract_plan > 0" class="alert alert-info mb-6"
@@ -40,12 +40,15 @@
class="xl:col-span-7" :customer="customer" />
<!-- You can add a placeholder for when the map isn't ready -->
<div v-else class="xl:col-span-7 bg-base-100 rounded-lg flex justify-center items-center">
<div v-else class="xl:col-span-7 card-glass flex justify-center items-center">
<p class="text-gray-400">Location not available...</p>
</div>
<ProfileSummary class="xl:col-span-5" :customer="customer" :automatic_status="automatic_status"
:customer_description="customer_description.description" @toggle-automatic="userAutomatic" />
<div class="xl:col-span-5 space-y-6">
<ProfileSummary :customer="customer" :automatic_status="automatic_status"
:customer_description="customer_description.description" @toggle-automatic="userAutomatic" />
<CustomerStats :stats="customer_stats" :last_delivery="customer_last_delivery" />
</div>
</div>
<HistoryTabs :deliveries="deliveries" :autodeliveries="autodeliveries" :service-calls="serviceCalls"
@@ -57,7 +60,7 @@
<!-- FIX: Changed `lg:` to `xl:` -->
<div class="xl:col-span-4 space-y-6">
<!-- Authorize.net Account Status Box -->
<div v-if="customer.id" class="bg-base-100 rounded-lg p-4 border">
<div v-if="customer.id" class="card-glass p-4">
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between">
<div class="flex items-center gap-3 mb-3 md:mb-0">
<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -101,14 +104,14 @@
<TankEstimation :customer-id="customer.id" />
<CustomerComments :comments="comments" @add-comment="onSubmitSocial" @delete-comment="deleteCustomerSocial" />
<CustomerStats :stats="customer_stats" :last_delivery="customer_last_delivery" />
<TankInfo :customer_id="customer.id" :tank="customer_tank" :description="customer_description" />
<TankInfo :customer_id="customer.id" :tank="customer_tank" :description="customer_description" :estimation="autoEstimation" />
<EquipmentParts :parts="currentParts" @open-parts-modal="openPartsModal" />
<CreditCards :cards="credit_cards" :count="credit_cards_count" :user_id="customer.id"
:auth_net_profile_id="customer.auth_net_profile_id" @edit-card="editCard" @remove-card="removeCard" />
<!-- Automatic Delivery Actions Box -->
<div v-if="automatic_status === 1 && autodeliveries.length > 0" class="bg-base-100 rounded-lg p-4 border">
<div v-if="automatic_status === 1 && autodeliveries.length > 0" class="card-glass p-4">
<h3 class="font-semibold mb-4">Automatic Delivery Actions</h3>
<div class="flex flex-wrap gap-2">
<router-link v-if="autodeliveries[0].auto_status != 3"
@@ -354,6 +357,7 @@ const autodeliveries = ref([] as AutomaticDelivery[])
const serviceCalls = ref([] as ServiceCall[])
const transactions = ref([] as AuthorizeTransaction[])
// --- END OF UPDATES ---
const autoEstimation = ref<{ confidence_score: number; k_factor_source: string; days_remaining: number } | undefined>(undefined)
const automatic_response = ref(0)
const credit_cards_count = ref(0)
const customer = ref({ id: 0, user_id: null as number | null, customer_first_name: '', customer_last_name: '', customer_town: '', customer_address: '', customer_state: 0, customer_zip: '', customer_apt: '', customer_home_type: 0, customer_phone_number: '', customer_latitude: 0, customer_longitude: 0, correct_address: true, account_number: '', auth_net_profile_id: null })
@@ -555,6 +559,18 @@ const getCustomerAutoDelivery = (userid: number) => {
deliveryService.auto.getProfileDeliveries(userid).then((response: AxiosResponse<any>) => {
autodeliveries.value = response.data || []
})
// Also fetch the auto delivery record for estimation data
deliveryService.auto.getByCustomer(userid).then((response: AxiosResponse<any>) => {
if (response.data && response.data.id) {
autoEstimation.value = {
confidence_score: response.data.confidence_score ?? 20,
k_factor_source: response.data.k_factor_source ?? 'default',
days_remaining: response.data.days_remaining ?? 999
}
}
}).catch(() => {
autoEstimation.value = undefined
})
}
const getCustomerDelivery = (userid: number, delivery_page: number) => {
@@ -1026,3 +1042,9 @@ const getAccountStatusMessage = (): string => {
}
}
</script>
<style scoped>
.card-glass {
@apply bg-gradient-to-br from-neutral/90 to-neutral/70 backdrop-blur-sm rounded-xl shadow-lg border border-base-content/5;
}
</style>