Major Refactor
This commit is contained in:
@@ -1,66 +1,125 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs mb-10">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
Service Calls
|
||||
</li>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li>Service Calls</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold mt-4">Service Calls</h1>
|
||||
|
||||
<div class="flex text-2xl mb-5 font-bold">
|
||||
Service Calls
|
||||
<!-- Main Content Card -->
|
||||
<div class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
|
||||
<!-- Header: Title and Count -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold">Upcoming and Active Service Calls</h2>
|
||||
<div v-if="!isLoading" class="badge badge-ghost">{{ services.length }} calls found</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div v-if="isLoading" class="text-center p-10">
|
||||
<span class="loading loading-spinner loading-lg"></span>
|
||||
<p class="mt-2">Loading service calls...</p>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else-if="services.length === 0" class="text-center p-10">
|
||||
<p>No active service calls found.</p>
|
||||
</div>
|
||||
|
||||
<!-- Data Display -->
|
||||
<div v-else>
|
||||
<!-- DESKTOP VIEW: Table -->
|
||||
<div class="overflow-x-auto hidden xl:block">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date / Time</th>
|
||||
<th>Customer</th>
|
||||
<th>Address</th>
|
||||
<th>Service Type</th>
|
||||
<th>Description</th>
|
||||
<th class="text-right">Cost</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Removed @click from tr to avoid conflicting actions -->
|
||||
<tr v-for="service in services" :key="service.id" class="hover">
|
||||
<td class="align-top">
|
||||
<div>{{ formatDate(service.scheduled_date) }}</div>
|
||||
<div class="text-xs opacity-70">{{ formatTime(service.scheduled_date) }}</div>
|
||||
</td>
|
||||
<td class="align-top">{{ service.customer_name }}</td>
|
||||
<td class="align-top">{{ service.customer_address }}, {{ service.customer_town }}</td>
|
||||
<td class="align-top">
|
||||
<span class="font-medium" :style="{ color: getServiceTypeColor(service.type_service_call) }">
|
||||
{{ getServiceTypeName(service.type_service_call) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="whitespace-normal text-sm align-top">
|
||||
<!-- TRUNCATION LOGIC FOR DESKTOP -->
|
||||
<div v-if="!isLongDescription(service.description) || isExpanded(service.id)">
|
||||
{{ service.description }}
|
||||
<a v-if="isLongDescription(service.description)" @click.prevent="toggleExpand(service.id)" href="#" class="link link-info link-hover text-xs ml-1 whitespace-nowrap">Show less</a>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ truncateDescription(service.description) }}
|
||||
<a @click.prevent="toggleExpand(service.id)" href="#" class="link link-info link-hover text-xs ml-1 whitespace-nowrap">Read more</a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right font-mono align-top">{{ formatCurrency(service.service_cost) }}</td>
|
||||
<td class="text-right align-top">
|
||||
<button @click="openEditModal(service)" class="btn btn-sm btn-primary">View</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- MOBILE VIEW: Cards -->
|
||||
<div class="xl:hidden space-y-4">
|
||||
<div v-for="service in services" :key="service.id" class="card bg-base-100 shadow-md">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="card-title text-base">{{ service.customer_name }}</h2>
|
||||
<p class="text-xs text-gray-400">{{ service.customer_address }}, {{ service.customer_town }}</p>
|
||||
</div>
|
||||
<div class="badge badge-outline text-right" :style="{ 'border-color': getServiceTypeColor(service.type_service_call), color: getServiceTypeColor(service.type_service_call) }">
|
||||
{{ getServiceTypeName(service.type_service_call) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-sm mt-2 grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<p><strong class="font-semibold">Date:</strong> {{ formatDate(service.scheduled_date) }}</p>
|
||||
<p><strong class="font-semibold">Time:</strong> {{ formatTime(service.scheduled_date) }}</p>
|
||||
<p><strong class="font-semibold">Cost:</strong> <span class="font-mono">{{ formatCurrency(service.service_cost) }}</span></p>
|
||||
</div>
|
||||
|
||||
<!-- TRUNCATION LOGIC FOR MOBILE -->
|
||||
<div v-if="service.description" class="text-sm mt-2 p-2 bg-base-200 rounded-md prose max-w-none">
|
||||
<div v-if="!isLongDescription(service.description) || isExpanded(service.id)">
|
||||
{{ service.description }}
|
||||
<a v-if="isLongDescription(service.description)" @click.prevent="toggleExpand(service.id)" href="#" class="link link-info link-hover text-xs ml-1 whitespace-nowrap">Show less</a>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ truncateDescription(service.description) }}
|
||||
<a @click.prevent="toggleExpand(service.id)" href="#" class="link link-info link-hover text-xs ml-1 whitespace-nowrap">Read more</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-actions justify-end mt-2">
|
||||
<button @click="openEditModal(service)" class="btn btn-sm btn-primary">View</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
<thead class="bg-base-200">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Scheduled Date</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Time</th>
|
||||
<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>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Service Type</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<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">{{ service.customer_name }}</td>
|
||||
|
||||
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ 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-normal text-sm">{{ service.service_cost }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -74,16 +133,13 @@
|
||||
@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'; // Import dayjs to handle date/time formatting
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
interface ServiceCall {
|
||||
id: number;
|
||||
@@ -99,13 +155,16 @@ interface ServiceCall {
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ServiceHome',
|
||||
components: { Header, SideBar, Footer, ServiceEditModal },
|
||||
components: { Footer, ServiceEditModal },
|
||||
data() {
|
||||
return {
|
||||
user: null,
|
||||
services: [] as ServiceCall[],
|
||||
isLoading: true,
|
||||
selectedServiceForEdit: null as ServiceCall | null,
|
||||
// --- ADDITIONS FOR TRUNCATION ---
|
||||
wordLimit: 50,
|
||||
expandedIds: [] as number[],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -154,7 +213,26 @@ export default defineComponent({
|
||||
closeEditModal() {
|
||||
this.selectedServiceForEdit = null;
|
||||
},
|
||||
|
||||
isLongDescription(text: string): boolean {
|
||||
if (!text) return false;
|
||||
return text.split(/\s+/).length > this.wordLimit;
|
||||
},
|
||||
truncateDescription(text: string): string {
|
||||
if (!this.isLongDescription(text)) return text;
|
||||
const words = text.split(/\s+/);
|
||||
return words.slice(0, this.wordLimit).join(' ') + '...';
|
||||
},
|
||||
isExpanded(id: number): boolean {
|
||||
return this.expandedIds.includes(id);
|
||||
},
|
||||
toggleExpand(id: number): void {
|
||||
const index = this.expandedIds.indexOf(id);
|
||||
if (index === -1) {
|
||||
this.expandedIds.push(id);
|
||||
} else {
|
||||
this.expandedIds.splice(index, 1);
|
||||
}
|
||||
},
|
||||
async handleSaveChanges(updatedService: ServiceCall) {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
|
||||
@@ -206,7 +284,17 @@ export default defineComponent({
|
||||
};
|
||||
return typeMap[typeId] || 'Unknown Service';
|
||||
},
|
||||
|
||||
// --- ADD THIS METHOD ---
|
||||
formatCurrency(value: string | number): string {
|
||||
if (value === null || value === undefined || value === '') return '$0.00';
|
||||
const numberValue = Number(value);
|
||||
if (isNaN(numberValue)) return '$0.00';
|
||||
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
}).format(numberValue);
|
||||
},
|
||||
getServiceTypeColor(typeId: number): string {
|
||||
const colorMap: { [key: number]: string } = {
|
||||
0: 'blue',
|
||||
|
||||
Reference in New Issue
Block a user