Files
eamco_office_frontend/src/pages/service/ServicePast.vue
2025-08-26 17:19:41 -04:00

246 lines
8.4 KiB
Vue

<template>
<Header />
<div class="flex">
<div class="">
<SideBar />
</div>
<div class="w-full px-10">
<div class="text-sm breadcrumbs mb-10">
<ul>
<li>
<router-link :to="{ name: 'home' }">
Home
</router-link>
</li>
<li>
PastService Calls
</li>
</ul>
</div>
<div class="flex text-2xl mb-5 font-bold">
Past Service Calls
</div>
<div v-if="isLoading" class="text-center p-10">
<p>Loading service calls...</p>
</div>
<div v-else-if="services.length === 0" class="text-center p-10 bg-base-200 rounded-md">
<p>No service calls found.</p>
</div>
<div v-else class="overflow-x-auto rounded-lg">
<table class="min-w-full divide-y divide-gray-700 table-fixed">
<!-- =================== THIS IS THE CORRECTED SECTION =================== -->
<thead class="bg-base-200">
<tr>
<!-- Columns with predictable, shorter content get fixed widths -->
<th scope="col" class="w-48 px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Scheduled Date</th>
<th scope="col" class="w-32 px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Time</th>
<!-- Columns with variable text content can share the remaining space -->
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Customer Name</th>
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Address</th>
<!-- Another fixed-width column -->
<th scope="col" class="w-32 px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Service Type</th>
<!-- Description can be left to fill remaining space -->
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Description</th>
<!-- Service Cost now has a smaller, fixed width -->
<th scope="col" class="w-20 px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Service Cost</th>
</tr>
</thead>
<!-- =================== END OF CORRECTED SECTION =================== -->
<tbody class="bg-base-100 divide-y divide-gray-700">
<tr v-for="service in services" :key="service.id" @click="openEditModal(service)" class="hover:bg-base-300 cursor-pointer">
<td class="px-6 py-4 whitespace-nowrap">{{ formatDate(service.scheduled_date) }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ formatTime(service.scheduled_date) }}</td>
<td class="px-6 py-4 whitespace-nowrap hover:text-blue-600 truncate">{{ service.customer_name }}</td>
<td class="px-6 py-4 whitespace-nowrap truncate">{{ service.customer_address }}, {{ service.customer_town }}</td>
<td class="px-6 py-4 whitespace-nowrap font-medium" :style="{ color: getServiceTypeColor(service.type_service_call) }">
{{ getServiceTypeName(service.type_service_call) }}
</td>
<td class="px-6 py-4 whitespace-normal text-sm">{{ service.description }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-right">{{ formatCurrency(service.service_cost) }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<Footer />
<ServiceEditModal
v-if="selectedServiceForEdit"
:service="selectedServiceForEdit"
@close-modal="closeEditModal"
@save-changes="handleSaveChanges"
@delete-service="handleDeleteService"
/>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import Header from '../../layouts/headers/headerauth.vue'
import SideBar from '../../layouts/sidebar/sidebar.vue'
import Footer from '../../layouts/footers/footer.vue'
import ServiceEditModal from './ServiceEditModal.vue'
import dayjs from 'dayjs';
interface ServiceCall {
id: number;
scheduled_date: string;
customer_id: number;
customer_name: string;
customer_address: string;
customer_town: string;
type_service_call: number;
description: string;
service_cost: string;
}
export default defineComponent({
name: 'ServiceHPast',
components: { Header, SideBar, Footer, ServiceEditModal },
data() {
return {
user: null,
services: [] as ServiceCall[],
isLoading: true,
selectedServiceForEdit: null as ServiceCall | null,
}
},
created() {
this.userStatus();
this.fetchUpcomingServices();
},
methods: {
// --- API and Data Handling Methods ---
async fetchUpcomingServices(): Promise<void> {
this.isLoading = true;
try {
const path = import.meta.env.VITE_BASE_URL + '/service/past';
const response = await axios.get(path, {
headers: authHeader(),
withCredentials: true,
});
this.services = response.data;
} catch (error) {
console.error("Failed to fetch upcoming service calls:", error);
} finally {
this.isLoading = false;
}
},
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) {
this.user = response.data.user;
}
})
.catch(() => {
this.user = null
})
},
openEditModal(service: ServiceCall) {
this.selectedServiceForEdit = service;
},
closeEditModal() {
this.selectedServiceForEdit = null;
},
async handleSaveChanges(updatedService: ServiceCall) {
try {
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
const response = await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
if (response.data.ok) {
const index = this.services.findIndex(s => s.id === updatedService.id);
if (index !== -1) {
this.services[index] = response.data.service;
}
this.closeEditModal();
}
} catch (error) {
console.error("Failed to save changes:", error);
alert("An error occurred while saving. Please check the console.");
}
},
async handleDeleteService(serviceId: number) {
try {
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
if (response.data.ok) {
this.services = this.services.filter(s => s.id !== serviceId);
this.closeEditModal();
}
} catch (error) {
console.error("Failed to delete service call:", error);
alert("An error occurred while deleting. Please check the console.");
}
},
// --- Formatting and Display Methods ---
formatCurrency(value: string): string {
if (!value) return '$0.00';
const costAsNumber = parseFloat(value);
if (isNaN(costAsNumber)) {
return value;
}
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
}).format(costAsNumber);
},
formatDate(dateString: string): string {
if (!dateString) return 'N/A';
return dayjs(dateString).format('MMMM D, YYYY');
},
formatTime(dateString: string): string {
if (!dateString) return 'N/A';
return dayjs(dateString).format('h:mm A');
},
getServiceTypeName(typeId: number): string {
const typeMap: { [key: number]: string } = {
0: 'Tune-up',
1: 'No Heat',
2: 'Fix',
3: 'Tank Install',
4: 'Other',
};
return typeMap[typeId] || 'Unknown Service';
},
getServiceTypeColor(typeId: number): string {
const colorMap: { [key: number]: string } = {
0: 'blue',
1: 'red',
2: 'green',
3: '#B58900',
4: 'black',
};
return colorMap[typeId] || 'gray';
}
},
})
</script>