Files
eamco_office_frontend/src/pages/customer/home.vue
Edwin Eames 72d8e35e06 refactor(frontend): migrate Customer domain to centralized API services
- Replaced all direct axios imports with service layer calls across 8 customer files
- Migrated core pages: home.vue, create.vue, edit.vue
- Migrated profile pages: profile.vue (1100+ lines), TankEstimation.vue
- Migrated supporting pages: ServicePlanEdit.vue, tank/edit.vue, list.vue

Services integrated:
- customerService: CRUD, descriptions, tank info, automatic status
- authService: authentication and Authorize.net account management
- paymentService: credit cards, transactions, payment authorization
- deliveryService: delivery records and automatic delivery data
- serviceService: service calls, parts, and service plans
- adminService: statistics, social comments, and reports
- queryService: dropdown data (customer types, states)

Type safety improvements:
- Updated paymentService.ts with accurate AxiosResponse types
- Fixed response unwrapping to match api.ts interceptor behavior
- Resolved all TypeScript errors in customer domain (0 errors)

Benefits:
- Consistent authentication via centralized interceptors
- Standardized error handling across all API calls
- Improved type safety with proper TypeScript interfaces
- Single source of truth for API endpoints
- Better testability through mockable services

Verified with vue-tsc --noEmit - all customer domain files pass type checking
2026-02-01 13:00:21 -05:00

189 lines
6.7 KiB
Vue
Executable File

<!-- src/pages/customer/home.vue -->
<template>
<div class="flex">
<div class="w-full px-4 md:px-10 ">
<!-- Breadcrumbs & Title -->
<div class="text-sm breadcrumbs">
<ul>
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
<li>Customers</li>
</ul>
</div>
<h1 class="text-3xl font-bold mt-4">Customers</h1>
<!-- Main Content Card -->
<div class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
<!-- Header: Search, Count, and Add Button -->
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
<!-- SEARCH AND COUNT (IMPROVED ALIGNMENT) -->
<div class="form-control">
<label class="label pt-1 pb-0">
<span class="label-text-alt">{{ customer_count }} customers found</span>
</label>
</div>
</div>
<div class="divider"></div>
<!-- DESKTOP VIEW: Table (Now breaks at XL) -->
<div class="overflow-x-auto hidden xl:block">
<table class="table w-full">
<thead>
<tr>
<th>Account #</th>
<th>Name</th>
<th>Town</th>
<th>Automatic</th>
<th>Phone Number</th>
</tr>
</thead>
<tbody>
<tr v-for="person in customers" :key="person.id" class="hover:bg-blue-600 hover:text-white">
<td>
<router-link v-if="person.id" :to="{ name: 'customerProfile', params: { id: person.id } }" class="link link-hover">
{{ person.account_number }}
</router-link>
<span v-else>{{ person.account_number }}</span>
</td>
<td>
<router-link v-if="person.id" :to="{ name: 'customerProfile', params: { id: person.id } }" class="link link-hover hover:text-green-500">{{ person.customer_first_name }} {{ person.customer_last_name }}</router-link>
<span v-else>{{ person.customer_first_name }} {{ person.customer_last_name }}</span>
</td>
<td>{{ person.customer_town }}</td>
<td><span :class="person.customer_automatic ? 'text-success' : 'text-gray-500'">{{ person.customer_automatic ? 'Yes' : 'No' }}</span></td>
<td>{{ person.customer_phone_number }}</td>
</tr>
</tbody>
</table>
</div>
<!-- MOBILE VIEW: Cards (Now breaks at XL) -->
<div class="xl:hidden space-y-4">
<div v-for="person in customers" :key="person.id" class="card bg-base-100 shadow-md">
<div class="card-body p-4">
<div class="flex justify-between items-start">
<div>
<router-link v-if="person.id" :to="{ name: 'customerProfile', params: { id: person.id } }" class="hover:text-green-500">
<h2 class="card-title text-base">{{ person.customer_first_name }} {{ person.customer_last_name }}</h2>
</router-link>
<h2 v-else class="card-title text-base">{{ person.customer_first_name }} {{ person.customer_last_name }}</h2>
<p class="text-xs text-gray-400">#{{ person.account_number }}</p>
</div>
<div class="badge" :class="person.customer_automatic ? 'badge-success' : 'badge-ghost'">
{{ person.customer_automatic ? 'Automatic' : 'Will Call' }}
</div>
</div>
<div class="text-sm mt-2">
<p>{{ person.customer_town }}</p>
<p>{{ person.customer_phone_number }}</p>
</div>
<div v-if="person.id" class="card-actions justify-end flex-wrap gap-2 mt-2">
<router-link :to="{ name: 'deliveryCreate', params: { id: person.id } }" class="btn btn-sm btn-primary">
New Delivery
</router-link>
<router-link :to="{ name: 'CalenderCustomer', params: { id: person.id } }" class="btn btn-sm btn-accent">
New Service
</router-link>
<router-link :to="{ name: 'customerEdit', params: { id: person.id } }" class="btn btn-sm btn-secondary">
Edit
</router-link>
<router-link :to="{ name: 'customerProfile', params: { id: person.id } }" class="btn btn-sm btn-ghost">
View
</router-link>
</div>
</div>
</div>
</div>
</div>
<!-- Pagination -->
<div class="mt-6 flex justify-center">
<pagination @paginate="getPage" :records="customer_count" v-model="page" :per-page="10" :options="options">
</pagination>
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, onMounted, markRaw } from 'vue'
import { customerService } from '../../services/customerService'
import { authService } from '../../services/authService'
import { Customer } from '../../types/models'
import Header from '../../layouts/headers/headerauth.vue'
import PaginationComp from '../../components/pagination.vue'
import SideBar from '../../layouts/sidebar/sidebar.vue'
import Footer from '../../layouts/footers/footer.vue'
// Reactive data
const token = ref(null)
const user = ref(null)
const customers = ref<Customer[]>([])
const customer_count = ref(0)
const page = ref(1)
const perPage = ref(50)
const recordsLength = ref(0)
const options = ref({
edgeNavigation: false,
format: false,
template: markRaw(PaginationComp)
})
// Functions
const getPage = (pageVal: any) => {
customers.value = [];
get_customers(pageVal)
get_customer_count()
}
const userStatus = () => {
authService.whoami()
.then((response: any) => {
if (response.data.ok) {
user.value = response.data.user;
}
})
.catch(() => {
user.value = null
})
}
const get_customers = async (pageVal: number) => {
try {
const response = await customerService.getAll(pageVal)
customers.value = response.data?.customers || []
} catch (error) {
console.error('Error fetching customers:', error)
customers.value = []
}
}
const get_customer_count = async () => {
try {
const response = await customerService.getCount()
if (response.data) {
customer_count.value = response.data.count
}
} catch (error) {
console.error('Error fetching customer count:', error)
}
}
const deleteCustomer = (user_id: number) => {
customerService.delete(user_id).then(() => {
get_customers(1)
})
}
// Lifecycle
onMounted(() => {
userStatus()
getPage(page.value)
})
</script>
<style scoped></style>