Working site
This commit is contained in:
258
src/pages/customer/profile/TankEstimation.vue
Normal file
258
src/pages/customer/profile/TankEstimation.vue
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
<!-- src/pages/customer/profile/TankEstimation.vue -->
|
||||||
|
<template>
|
||||||
|
<div class="bg-base-100 rounded-lg p-4 border">
|
||||||
|
<h3 class="font-semibold mb-4 flex items-center gap-2">
|
||||||
|
<svg class="w-5 h-5 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
||||||
|
</svg>
|
||||||
|
Fuel Tank Estimation
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div v-if="loading" class="flex justify-center items-center py-8">
|
||||||
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="error" class="text-center py-4">
|
||||||
|
<p class="text-sm text-red-600">
|
||||||
|
<span class="font-medium">Estimation Error:</span> {{ error }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="estimation">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<!-- Tank Level Display -->
|
||||||
|
<div>
|
||||||
|
<div class="flex justify-between items-center mb-2">
|
||||||
|
<label class="label-text font-medium">Current Tank Level</label>
|
||||||
|
<span class="text-sm text-gray-500">{{ Math.round(estimation.estimated_gallons) }} / {{ Math.round(estimation.tank_size) }} gal</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<progress
|
||||||
|
class="progress w-full"
|
||||||
|
:value="estimation.estimated_gallons"
|
||||||
|
:max="estimation.tank_size"
|
||||||
|
:class="{
|
||||||
|
'progress-success': getTankLevelPercentage() > 60,
|
||||||
|
'progress-warning': getTankLevelPercentage() >= 25 && getTankLevelPercentage() <= 60,
|
||||||
|
'progress-error': getTankLevelPercentage() < 25
|
||||||
|
}"
|
||||||
|
></progress>
|
||||||
|
|
||||||
|
<div class="flex justify-between text-xs text-gray-500 mt-1">
|
||||||
|
<span>Empty</span>
|
||||||
|
<span>{{ Math.round(getTankLevelPercentage()) }}% Full</span>
|
||||||
|
<span>Full</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Usage Information -->
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="label-text font-medium">Usage Factor</label>
|
||||||
|
<div class="text-lg font-mono">{{ getScalingFactorCategory(estimation.scaling_factor) }}</div>
|
||||||
|
<div class="text-xs text-gray-500">{{ formatScalingFactor(estimation.scaling_factor) }} gallons per degree day</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="label-text font-medium">Daily Usage</label>
|
||||||
|
<div class="text-lg font-mono">{{ calculateDailyUsage() }}</div>
|
||||||
|
<div class="text-xs text-gray-500">Gallons per day</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="text-center py-8 text-gray-500">
|
||||||
|
<svg class="w-12 h-12 mx-auto mb-4 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
||||||
|
</svg>
|
||||||
|
<p>No fuel estimation data available</p>
|
||||||
|
<p class="text-xs mt-1">Run fuel estimation to see tank levels</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue'
|
||||||
|
import axios from 'axios'
|
||||||
|
import authHeader from '../../../services/auth.header'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
|
interface FuelEstimation {
|
||||||
|
id: number;
|
||||||
|
customer_id: number;
|
||||||
|
total_deliveries: number;
|
||||||
|
customer_full_name: string;
|
||||||
|
account_number: string;
|
||||||
|
address: string;
|
||||||
|
estimated_gallons: number;
|
||||||
|
tank_size: number;
|
||||||
|
scaling_factor: number | null;
|
||||||
|
last_5_deliveries: Array<{
|
||||||
|
fill_date: string;
|
||||||
|
gallons_delivered: number;
|
||||||
|
price_per_gallon: number | null;
|
||||||
|
total_amount_customer: number;
|
||||||
|
}>;
|
||||||
|
last_fill?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'TankEstimation',
|
||||||
|
props: {
|
||||||
|
customerId: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
estimation: null as FuelEstimation | null,
|
||||||
|
loading: true,
|
||||||
|
error: null as string | null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchEstimation()
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
customerId: {
|
||||||
|
handler(newId, oldId) {
|
||||||
|
if (newId !== oldId) {
|
||||||
|
console.log('Customer ID changed from', oldId, 'to', newId)
|
||||||
|
this.fetchEstimation()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
immediate: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async fetchEstimation() {
|
||||||
|
this.loading = true
|
||||||
|
this.error = null
|
||||||
|
this.estimation = null // Clear previous data
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('Fetching estimation for customer ID:', this.customerId)
|
||||||
|
|
||||||
|
// First check if customer is automatic
|
||||||
|
const customerPath = `${import.meta.env.VITE_BASE_URL}/customer/${this.customerId}`
|
||||||
|
console.log('Checking customer type:', customerPath)
|
||||||
|
const customerResponse = await axios.get(customerPath, { headers: authHeader() })
|
||||||
|
const isAutomatic = customerResponse.data.customer_automatic === 1
|
||||||
|
|
||||||
|
console.log('Customer automatic status:', isAutomatic, customerResponse.data)
|
||||||
|
|
||||||
|
let path: string
|
||||||
|
if (isAutomatic) {
|
||||||
|
// Fetch from automatic delivery API
|
||||||
|
path = `${import.meta.env.VITE_AUTO_URL}/delivery/auto/customer/${this.customerId}`
|
||||||
|
console.log('Fetching automatic data from:', path)
|
||||||
|
} else {
|
||||||
|
// Fetch from customer estimation API
|
||||||
|
path = `${import.meta.env.VITE_AUTO_URL}/fixstuff_customer/estimate_gallons/customer/${this.customerId}`
|
||||||
|
console.log('Fetching customer data from:', path)
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.get(path, { headers: authHeader() })
|
||||||
|
console.log('API Response:', response.data)
|
||||||
|
|
||||||
|
if (response.data.error) {
|
||||||
|
this.error = response.data.error
|
||||||
|
console.error('API returned error:', response.data.error)
|
||||||
|
} else {
|
||||||
|
if (isAutomatic) {
|
||||||
|
// Transform automatic delivery data to match our interface
|
||||||
|
if (response.data && response.data.id) {
|
||||||
|
const autoData = response.data
|
||||||
|
console.log('Processing automatic data:', autoData)
|
||||||
|
this.estimation = {
|
||||||
|
id: autoData.id,
|
||||||
|
customer_id: autoData.customer_id,
|
||||||
|
total_deliveries: 0, // Not available in auto data
|
||||||
|
customer_full_name: autoData.customer_full_name,
|
||||||
|
account_number: autoData.account_number,
|
||||||
|
address: autoData.customer_address,
|
||||||
|
estimated_gallons: autoData.estimated_gallons_left,
|
||||||
|
tank_size: autoData.tank_size,
|
||||||
|
scaling_factor: autoData.house_factor,
|
||||||
|
last_5_deliveries: [],
|
||||||
|
last_fill: autoData.last_fill
|
||||||
|
}
|
||||||
|
console.log('Set automatic estimation:', this.estimation)
|
||||||
|
} else {
|
||||||
|
console.warn('No automatic delivery data found for customer', this.customerId)
|
||||||
|
this.error = 'No automatic delivery data available'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Setting customer estimation:', response.data)
|
||||||
|
this.estimation = response.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Failed to fetch fuel estimation:', error)
|
||||||
|
if (error.response?.status === 404) {
|
||||||
|
this.error = 'Customer data not found'
|
||||||
|
} else if (error.response?.data?.error) {
|
||||||
|
this.error = error.response.data.error
|
||||||
|
} else {
|
||||||
|
this.error = 'Failed to load fuel estimation data'
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getTankLevelPercentage(): number {
|
||||||
|
if (!this.estimation || !this.estimation.tank_size || this.estimation.tank_size === 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return (this.estimation.estimated_gallons / this.estimation.tank_size) * 100
|
||||||
|
},
|
||||||
|
|
||||||
|
calculateDailyUsage(): string {
|
||||||
|
if (!this.estimation || !this.estimation.scaling_factor) {
|
||||||
|
return 'N/A'
|
||||||
|
}
|
||||||
|
// For a typical day with ~20 degree days (moderate winter day)
|
||||||
|
const typicalDegreeDays = 20
|
||||||
|
const dailyUsage = this.estimation.scaling_factor * typicalDegreeDays
|
||||||
|
return dailyUsage.toFixed(1)
|
||||||
|
},
|
||||||
|
|
||||||
|
formatScalingFactor(scalingFactor: number | null): string {
|
||||||
|
if (scalingFactor === null || scalingFactor === undefined) {
|
||||||
|
return 'N/A'
|
||||||
|
}
|
||||||
|
return scalingFactor.toFixed(2)
|
||||||
|
},
|
||||||
|
|
||||||
|
getScalingFactorCategory(scalingFactor: number | null): string {
|
||||||
|
if (scalingFactor === null || scalingFactor === undefined) {
|
||||||
|
return 'N/A'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scalingFactor < 0.05) {
|
||||||
|
return 'Very Low'
|
||||||
|
} else if (scalingFactor < 0.1) {
|
||||||
|
return 'Low'
|
||||||
|
} else if (scalingFactor < 0.2) {
|
||||||
|
return 'Normal'
|
||||||
|
} else if (scalingFactor < 0.5) {
|
||||||
|
return 'High'
|
||||||
|
} else {
|
||||||
|
return 'Very High'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
formatDate(dateString: string): string {
|
||||||
|
if (!dateString) return 'N/A'
|
||||||
|
return dayjs(dateString).format('MMM D, YYYY')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -98,6 +98,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<TankEstimation :customer-id="customer.id" />
|
||||||
|
|
||||||
<CustomerComments :comments="comments" @add-comment="onSubmitSocial" @delete-comment="deleteCustomerSocial" />
|
<CustomerComments :comments="comments" @add-comment="onSubmitSocial" @delete-comment="deleteCustomerSocial" />
|
||||||
<CustomerStats :stats="customer_stats" :last_delivery="customer_last_delivery" />
|
<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" />
|
||||||
@@ -264,6 +266,7 @@ import EquipmentParts from './profile/EquipmentParts.vue';
|
|||||||
import CreditCards from './profile/CreditCards.vue';
|
import CreditCards from './profile/CreditCards.vue';
|
||||||
import CustomerComments from './profile/CustomerComments.vue';
|
import CustomerComments from './profile/CustomerComments.vue';
|
||||||
import HistoryTabs from './profile/HistoryTabs.vue';
|
import HistoryTabs from './profile/HistoryTabs.vue';
|
||||||
|
import TankEstimation from './TankEstimation.vue';
|
||||||
|
|
||||||
|
|
||||||
L.Icon.Default.mergeOptions({
|
L.Icon.Default.mergeOptions({
|
||||||
@@ -352,6 +355,7 @@ export default defineComponent({
|
|||||||
CreditCards,
|
CreditCards,
|
||||||
CustomerComments,
|
CustomerComments,
|
||||||
HistoryTabs,
|
HistoryTabs,
|
||||||
|
TankEstimation,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -50,9 +50,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="grid grid-cols-12">
|
<div class="grid grid-cols-12">
|
||||||
<div class="col-span-6 ">
|
<div class="col-span-6 ">
|
||||||
<div class="col-span-12 pl-5">Auburn Oil</div>
|
<div class="col-span-12 pl-5">Auburn Oil</div>
|
||||||
@@ -90,7 +87,7 @@
|
|||||||
<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-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" >{{todays_price }}</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 pl-4 pt-4" > </div>
|
||||||
<div class="col-span-12 h-7 pt-6"></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"></div>
|
||||||
|
|||||||
Reference in New Issue
Block a user