Files
eamco_office_frontend/src/pages/Index.vue
Edwin Eames 61f93ec4e8 Refactor frontend to Composition API and improve UI/UX
Major Changes:
- Migrate components from Options API to Composition API with <script setup>
- Add centralized service layer (serviceService, deliveryService, adminService)
- Implement new reusable components (EnhancedButton, EnhancedModal, StatCard, etc.)
- Add theme store for consistent theming across application
- Improve ServiceCalendar with federal holidays and better styling
- Refactor customer profile and tank estimation components
- Update all delivery and payment pages to use centralized services
- Add utility functions for formatting and validation
- Update Dockerfiles for better environment configuration
- Enhance Tailwind config with custom design tokens

UI Improvements:
- Modern, premium design with glassmorphism effects
- Improved form layouts with FloatingInput components
- Better loading states and empty states
- Enhanced modals and tables with consistent styling
- Responsive design improvements across all pages

Technical Improvements:
- Strict TypeScript types throughout
- Better error handling and validation
- Removed deprecated api.js in favor of TypeScript services
- Improved code organization and maintainability
2026-02-01 19:04:07 -05:00

338 lines
13 KiB
Vue
Executable File

<!-- src/pages/Index.vue -->
<template>
<div class="flex">
<div class="w-full px-4 md:px-10 ">
<!-- Breadcrumbs & Welcome Header -->
<div class="text-sm breadcrumbs">
<ul>
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
</ul>
</div>
<h1 class="text-3xl font-bold mt-4">
Welcome, {{ employee.employee_first_name }}!
</h1>
<!-- Main Dashboard Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6 my-6 animate-fade-in">
<!-- Card 1: Today's Deliveries -->
<div class="bg-gradient-to-br from-neutral to-neutral/80 rounded-xl p-6 shadow-medium hover-lift xl:col-span-2">
<div class="flex items-center justify-between mb-4">
<h3 class="text-xl font-semibold">Today's Deliveries</h3>
<div class="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-primary">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 18.75a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h6m-9 0H3.375a1.125 1.125 0 01-1.125-1.125V14.25m17.25 4.5a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h1.125c.621 0 1.129-.504 1.09-1.124a17.902 17.902 0 00-3.213-9.193 2.056 2.056 0 00-1.58-.86H14.25M16.5 18.75h-2.25m0-11.177v-.958c0-.568-.422-1.048-.987-1.106a48.554 48.554 0 00-10.026 0 1.106 1.106 0 00-.987 1.106v7.635m12-6.677v6.677m0 4.5v-4.5m0 0h-12" />
</svg>
</div>
</div>
<div class="space-y-4">
<div class="flex items-baseline gap-2">
<span class="text-4xl font-bold">{{ delivery_count }}</span>
<span class="text-base-content/60">total deliveries</span>
</div>
<div>
<div class="flex justify-between text-sm mb-2">
<span class="font-medium">Completed</span>
<span class="font-mono">{{ delivery_count_delivered }} / {{ delivery_count }}</span>
</div>
<progress class="progress progress-primary w-full h-3" :value="delivery_count_delivered" :max="delivery_count"></progress>
</div>
</div>
</div>
<!-- Card 2: Today's Oil Price -->
<div class="bg-gradient-to-br from-neutral to-neutral/80 rounded-xl p-6 shadow-medium hover-lift">
<div class="flex items-center justify-between mb-4">
<h3 class="text-xl font-semibold">Oil Pricing</h3>
<div class="w-12 h-12 rounded-full bg-warning/10 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-warning">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m-3-2.818l.879.659c1.171.879 3.07.879 4.242 0 1.172-.879 1.172-2.303 0-3.182C13.536 12.219 12.768 12 12 12c-.725 0-1.45-.22-2.003-.659-1.106-.879-1.106-2.303 0-3.182s2.9-.879 4.006 0l.415.33M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
<div class="space-y-3">
<div class="flex justify-between items-center">
<span class="text-sm text-base-content/70">Per Gallon</span>
<span class="text-2xl font-bold font-mono">${{ today_oil_price }}</span>
</div>
<div class="divider my-2"></div>
<div class="space-y-2 text-sm">
<div class="flex justify-between">
<span class="text-base-content/70">Same Day</span>
<span class="font-mono font-semibold">${{ price_same_day }}</span>
</div>
<div class="flex justify-between">
<span class="text-base-content/70">Prime</span>
<span class="font-mono font-semibold">${{ price_prime }}</span>
</div>
<div class="flex justify-between">
<span class="text-base-content/70">Emergency</span>
<span class="font-mono font-semibold">${{ price_emergency }}</span>
</div>
</div>
</div>
</div>
<!-- Card 3: Service Pricing -->
<div class="bg-gradient-to-br from-neutral to-neutral/80 rounded-xl p-6 shadow-medium hover-lift">
<div class="flex items-center justify-between mb-4">
<h3 class="text-xl font-semibold">Service Pricing</h3>
<div class="w-12 h-12 rounded-full bg-info/10 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-info">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.42 15.17L17.25 21A2.652 2.652 0 0021 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 11-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 004.486-6.336l-3.276 3.277a3.004 3.004 0 01-2.25-2.25l3.276-3.276a4.5 4.5 0 00-6.336 4.486c.091 1.076-.071 2.264-.904 2.95l-.102.085m-1.745 1.437L5.909 7.5H4.5L2.25 3.75l1.5-1.5L7.5 4.5v1.409l4.26 4.26m-1.745 1.437l1.745-1.437m6.615 8.206L15.75 15.75M4.867 19.125h.008v.008h-.008v-.008z" />
</svg>
</div>
</div>
<div class="space-y-4">
<div class="flex justify-between items-center">
<span class="text-sm text-base-content/70">Per Hour</span>
<span class="text-2xl font-bold font-mono">$125</span>
</div>
<div class="flex justify-between items-center">
<span class="text-sm text-base-content/70">Emergency</span>
<span class="text-2xl font-bold font-mono">$200</span>
</div>
</div>
</div>
<!-- Card 4: Search Shortcuts -->
<div class="bg-gradient-to-br from-neutral to-neutral/80 rounded-xl p-6 shadow-medium hover-lift">
<div class="flex items-center justify-between mb-4">
<h3 class="text-xl font-semibold">Search Shortcuts</h3>
<div class="w-12 h-12 rounded-full bg-accent/10 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-accent">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
</svg>
</div>
</div>
<div class="space-y-3 text-sm">
<div class="flex items-center gap-3">
<kbd class="kbd kbd-sm">@</kbd>
<span class="text-base-content/70">Last name</span>
</div>
<div class="flex items-center gap-3">
<kbd class="kbd kbd-sm">!</kbd>
<span class="text-base-content/70">Address</span>
</div>
<div class="flex items-center gap-3">
<kbd class="kbd kbd-sm">#</kbd>
<span class="text-base-content/70">Phone number</span>
</div>
<div class="flex items-center gap-3">
<kbd class="kbd kbd-sm">$</kbd>
<span class="text-base-content/70">Account number</span>
</div>
</div>
</div>
<!-- Card 5: This Week's Stats -->
<!-- <div class="bg-neutral rounded-lg p-5 xl:col-span-4">
<h3 class="text-xl font-bold mb-4">This Week's Stats</h3>
<div class="stats stats-vertical lg:stats-horizontal shadow bg-base-100 w-full">
<div class="stat">
<div class="stat-title">Total Deliveries</div>
<div class="stat-value">{{ total_deliveries }}</div>
<div class="stat-desc">In the last 7 days</div>
</div>
<div class="stat">
<div class="stat-title">Total Gallons</div>
<div class="stat-value">{{ total_gallons_past_week }}</div>
<div class="stat-desc">Delivered this week</div>
</div>
<div class="stat">
<div class="stat-title">Total Profit</div>
<div class="stat-value text-success">${{ total_profit_past_week }}</div>
<div class="stat-desc">Estimated earnings</div>
</div>
</div>
</div> -->
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import axios from 'axios'
import authHeader from '../services/auth.header'
import Header from '../layouts/headers/headerauth.vue'
import SideBar from '../layouts/sidebar/sidebar.vue'
// Props
const props = defineProps<{
clickCount?: number
}>()
// Router
const router = useRouter()
// Reactive data
const token = ref(null)
const call_count = ref(0)
const delivery_count = ref(0)
const delivery_count_delivered = ref(0)
const price_from_supplier = ref(0)
const today_oil_price = ref(0)
const price_for_employee = ref(0)
const price_same_day = ref(0)
const price_prime = ref(0)
const price_emergency = ref(0)
const user = ref({
user_id: 0,
user_name: '',
})
const employee = ref({
id: '',
user_id: '',
employee_last_name: "",
employee_first_name: "",
employee_town: "",
employee_address: "",
employee_apt: "",
employee_zip: "",
employee_birthday: "",
employee_phone_number: "",
employee_start_date: "",
employee_end_date: "",
employee_type: '',
employee_state: '',
})
const total_gallons_past_week = ref(0)
const total_profit_past_week = ref(0)
const total_deliveries = ref(0)
const loaded = ref(false)
// Lifecycle
onMounted(() => {
userStatus()
today_delivery_count()
today_delivery_delivered()
today_price_oil()
totalgallonsweek()
totalprofitweek()
})
// Functions
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
if (response.data.ok) {
user.value = response.data.user;
employeeStatus()
} else {
localStorage.removeItem('user');
router.push('/login');
}
})
}
const totalgallonsweek = () => {
let path = import.meta.env.VITE_BASE_URL + '/stats/gallons/week';
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
total_gallons_past_week.value = response.data.total;
})
}
const totalprofitweek = () => {
let path = import.meta.env.VITE_BASE_URL + '/money/profit/week';
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
total_profit_past_week.value = response.data.total_profit;
total_deliveries.value = response.data.total_deliveries;
})
}
const employeeStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/employee/userid/' + user.value.user_id;
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
employee.value = response.data?.employee || response.data;
loaded.value = true;
})
}
const total_calls = () => {
let path = import.meta.env.VITE_BASE_URL + '/stats/call/count/today'
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
call_count.value = response.data.data;
})
}
const today_delivery_count = () => {
let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/today'
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
delivery_count.value = response.data.data;
})
}
const today_delivery_delivered = () => {
let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/delivered/today'
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
console.log(response.data)
delivery_count_delivered.value = response.data.data;
})
}
const today_price_oil = () => {
let path = import.meta.env.VITE_BASE_URL + '/info/price/oil'
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
price_from_supplier.value = response.data.price_from_supplier;
today_oil_price.value = response.data.price_for_customer;
price_for_employee.value = response.data.price_for_employee;
price_same_day.value = response.data.price_same_day;
price_prime.value = response.data.price_prime;
price_emergency.value = response.data.price_emergency;
})
}
</script>