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:
@@ -29,51 +29,58 @@
|
||||
<div class="text-xl font-bold">{{ autoTicket.customer_full_name }}</div>
|
||||
<div class="text-sm text-gray-400">Account: {{ autoTicket.account_number }}</div>
|
||||
</div>
|
||||
<router-link v-if="customer && customer.id" :to="{ name: 'customerProfile', params: { id: customer.id } }" class="btn btn-secondary btn-sm">
|
||||
<router-link v-if="customer && customer.id" :to="{ name: 'customerProfile', params: { id: customer.id } }"
|
||||
class="btn btn-secondary btn-sm">
|
||||
View Profile
|
||||
</router-link>
|
||||
</div>
|
||||
<div>
|
||||
<div>{{ autoTicket.customer_address }}</div>
|
||||
<div v-if="autoTicket.customer_apt && autoTicket.customer_apt !== 'None'">Apt: {{ autoTicket.customer_apt }}</div>
|
||||
<div>
|
||||
{{ autoTicket.customer_town }},
|
||||
<span v-if="autoTicket.customer_state == 0">Massachusetts</span>
|
||||
<span v-else-if="autoTicket.customer_state == 1">Rhode Island</span>
|
||||
<span v-else-if="autoTicket.customer_state == 2">New Hampshire</span>
|
||||
<span v-else-if="autoTicket.customer_state == 3">Maine</span>
|
||||
<span v-else-if="autoTicket.customer_state == 4">Vermont</span>
|
||||
<span v-else-if="autoTicket.customer_state == 5">Connecticut</span>
|
||||
<span v-else-if="autoTicket.customer_state == 6">New York</span>
|
||||
<span v-else>Unknown state</span>
|
||||
{{ autoTicket.customer_zip }}
|
||||
<div class="flex items-start gap-2">
|
||||
<a :href="getMapLink(autoTicket)" target="_blank"
|
||||
class="btn btn-ghost btn-xs btn-circle text-primary mt-1" title="View on Map">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4">
|
||||
<path fill-rule="evenodd"
|
||||
d="M9.69 18.933l.003.001C9.89 19.02 10 19 10 19s.11.02.308-.066l.002-.001.006-.003.018-.008a5.741 5.741 0 00.281-.14c.186-.096.446-.24.757-.433.62-.384 1.445-.966 2.274-1.765C15.302 14.988 17 12.493 17 9A7 7 0 103 9c0 3.492 1.698 5.988 3.355 7.584a13.731 13.731 0 002.273 1.765 11.842 11.842 0 00.976.544l.062.029.018.008.006.003zM10 11.25a2.25 2.25 0 100-4.5 2.25 2.25 0 000 4.5z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</a>
|
||||
<div>
|
||||
<div>{{ autoTicket.customer_address }}</div>
|
||||
<div v-if="autoTicket.customer_apt && autoTicket.customer_apt !== 'None'">Apt: {{
|
||||
autoTicket.customer_apt }}</div>
|
||||
<div>
|
||||
{{ autoTicket.customer_town }},
|
||||
<span>{{ getStateName(autoTicket.customer_state) }}</span>
|
||||
{{ autoTicket.customer_zip }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-gray-400 mt-1">
|
||||
<div class="text-sm text-gray-400 mt-1 ml-8">
|
||||
Auto Delivery
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Payment Status Card -->
|
||||
<div class="bg-neutral rounded-lg p-5">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="col-span-2">
|
||||
<div class="font-bold text-sm">Payment Status</div>
|
||||
<div class="badge badge-lg"
|
||||
:class="{
|
||||
'badge-success': autoTicket.payment_status === PAYMENT_STATUS.PAID,
|
||||
'badge-info': autoTicket.payment_status === PAYMENT_STATUS.PRE_AUTHORIZED,
|
||||
'badge-error': autoTicket.payment_status === PAYMENT_STATUS.UNPAID || autoTicket.payment_status == null,
|
||||
'badge-warning': [PAYMENT_STATUS.PROCESSING, PAYMENT_STATUS.FAILED].includes(autoTicket.payment_status)
|
||||
}">
|
||||
<span v-if="autoTicket.payment_status === PAYMENT_STATUS.UNPAID || autoTicket.payment_status == null">Unpaid</span>
|
||||
<span v-else-if="autoTicket.payment_status === PAYMENT_STATUS.PRE_AUTHORIZED">Pre-authorized</span>
|
||||
<span v-else-if="autoTicket.payment_status === PAYMENT_STATUS.PROCESSING">Processing</span>
|
||||
<span v-else-if="autoTicket.payment_status === PAYMENT_STATUS.PAID">Paid</span>
|
||||
<span v-else-if="autoTicket.payment_status === PAYMENT_STATUS.FAILED">Failed</span>
|
||||
<span v-else>Unknown</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Payment Status Card -->
|
||||
<div class="bg-neutral rounded-lg p-5">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="col-span-2">
|
||||
<div class="font-bold text-sm">Payment Status</div>
|
||||
<div class="badge badge-lg" :class="{
|
||||
'badge-success': autoTicket.payment_status === PAYMENT_STATUS.PAID,
|
||||
'badge-info': autoTicket.payment_status === PAYMENT_STATUS.PRE_AUTHORIZED,
|
||||
'badge-error': autoTicket.payment_status === PAYMENT_STATUS.UNPAID || autoTicket.payment_status == null,
|
||||
'badge-warning': [PAYMENT_STATUS.PROCESSING, PAYMENT_STATUS.FAILED].includes(autoTicket.payment_status)
|
||||
}">
|
||||
<span
|
||||
v-if="autoTicket.payment_status === PAYMENT_STATUS.UNPAID || autoTicket.payment_status == null">Unpaid</span>
|
||||
<span v-else-if="autoTicket.payment_status === PAYMENT_STATUS.PRE_AUTHORIZED">Pre-authorized</span>
|
||||
<span v-else-if="autoTicket.payment_status === PAYMENT_STATUS.PROCESSING">Processing</span>
|
||||
<span v-else-if="autoTicket.payment_status === PAYMENT_STATUS.PAID">Paid</span>
|
||||
<span v-else-if="autoTicket.payment_status === PAYMENT_STATUS.FAILED">Failed</span>
|
||||
<span v-else>Unknown</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-bold text-sm">Fill Date</div>
|
||||
<div>{{ autoTicket.fill_date }}</div>
|
||||
@@ -103,7 +110,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pricing & Totals -->
|
||||
<!-- Pricing & Totals -->
|
||||
<div class="p-4 border rounded-md space-y-2">
|
||||
<label class="label-text font-bold">Financial Summary</label>
|
||||
<div class="space-y-2">
|
||||
@@ -141,7 +148,8 @@
|
||||
<div class="flex justify-between">
|
||||
<span>Type:</span>
|
||||
<span :class="getTypeColor(transaction.transaction_type)">
|
||||
{{ transaction.transaction_type === 0 ? 'Charge' : transaction.transaction_type === 1 ? 'Auth' : transaction.transaction_type === 2 ? 'Capture' : 'Other' }}
|
||||
{{ transaction.transaction_type === 0 ? 'Charge' : transaction.transaction_type === 1 ? 'Auth' :
|
||||
transaction.transaction_type === 2 ? 'Capture' : 'Other' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
@@ -200,8 +208,8 @@
|
||||
|
||||
<!-- Card display -->
|
||||
<div v-if="userCardfound && [1, 2, 3].includes(autoTicket.payment_type)"
|
||||
class="p-4 rounded-lg border mt-2"
|
||||
:class="userCard.main_card ? 'bg-primary/10 border-primary' : 'bg-base-200 border-base-300'">
|
||||
class="p-4 rounded-lg border mt-2"
|
||||
:class="userCard.main_card ? 'bg-primary/10 border-primary' : 'bg-base-200 border-base-300'">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<div class="font-bold">{{ userCard.name_on_card }}</div>
|
||||
@@ -214,7 +222,8 @@
|
||||
<p>{{ userCard.card_number }}</p>
|
||||
<p>
|
||||
Exp:
|
||||
<span v-if="Number(userCard.expiration_month) < 10">0</span>{{ userCard.expiration_month }} / {{ userCard.expiration_year }}
|
||||
<span v-if="Number(userCard.expiration_month) < 10">0</span>{{ userCard.expiration_month }} / {{
|
||||
userCard.expiration_year }}
|
||||
</p>
|
||||
<p>CVV: {{ userCard.security_number }}</p>
|
||||
</div>
|
||||
@@ -226,17 +235,18 @@
|
||||
<div class="p-4 border rounded-md">
|
||||
<label class="label-text font-bold">Notes</label>
|
||||
<div class="prose prose-sm mt-4 max-w-none">
|
||||
<blockquote class="text-gray-400">Auto delivery processed automatically based on tank levels.</blockquote>
|
||||
<blockquote class="text-gray-400">Auto delivery processed automatically based on tank levels.
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
@@ -249,13 +259,15 @@ import {
|
||||
getPaymentStatusLabel,
|
||||
getTransactionStatusLabel
|
||||
} from '../../constants/status';
|
||||
import { STATE_ID_TO_NAME } from '../../constants/states';
|
||||
import { getGoogleMapsLink } from '../../utils/addressUtils';
|
||||
|
||||
import Header from '../../layouts/headers/headerauth.vue'
|
||||
import SideBar from '../../layouts/sidebar/sidebar.vue'
|
||||
import useValidate from "@vuelidate/core";
|
||||
import { notify } from "@kyvg/vue3-notification"
|
||||
import dayjs from 'dayjs';
|
||||
import {AutoDelivery, Customer, AuthorizeTransaction, CreditCard} from '../../types/models';
|
||||
import { AutoDelivery, Customer, AuthorizeTransaction, CreditCard } from '../../types/models';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'automaticDeliveryView',
|
||||
@@ -294,6 +306,20 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
methods: {
|
||||
// Helper methods for template
|
||||
getStateName(stateId: number) {
|
||||
return STATE_ID_TO_NAME[stateId] || 'Unknown state';
|
||||
},
|
||||
getMapLink(ticket: any) {
|
||||
if (!ticket) return '#';
|
||||
return getGoogleMapsLink(
|
||||
ticket.customer_address,
|
||||
ticket.customer_town,
|
||||
ticket.customer_state,
|
||||
ticket.customer_zip
|
||||
);
|
||||
},
|
||||
|
||||
format_date(value: string) {
|
||||
if (value) {
|
||||
return dayjs(String(value)).format('LLLL')
|
||||
@@ -402,5 +428,4 @@ export default defineComponent({
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
||||
Reference in New Issue
Block a user