Major Refactor

This commit is contained in:
2025-09-01 16:42:44 -04:00
parent 76cbca94e3
commit 992a1a217d
69 changed files with 12683 additions and 8082 deletions

View File

@@ -1,9 +1,6 @@
<template>
<Header />
<div class="flex">
<div class="">
<SideBar />
</div>
<div class="w-full px-10">
<div class="text-sm breadcrumbs mb-4">
<ul>
@@ -78,7 +75,7 @@ export default defineComponent({
events: `${import.meta.env.VITE_BASE_URL}/service/all`,
eventClick: this.handleEventClick,
// Add headers for authentication if needed by your API
eventSourceSuccess: (content, response) => {
eventSourceSuccess: (content) => {
// This is where you could transform data if needed
return content;
},

View File

@@ -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',

View File

@@ -1,75 +1,126 @@
<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>
PastService Calls
</li>
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
<li>Past Service 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>
<!-- 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">Service Call History</h2>
<div v-if="!isLoading" class="badge badge-ghost">{{ services.length }} calls found</div>
</div>
<div class="divider"></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>
<!-- 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>
<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>
<!-- Empty State -->
<div v-else-if="services.length === 0" class="text-center p-10">
<p>No past service calls found.</p>
</div>
<!-- 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 =================== -->
<!-- Data Display -->
<div v-else>
<!-- DESKTOP VIEW: Table (Revamped) -->
<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 with "Read more" -->
<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">
<!-- Moved @click handler to the button for explicit action -->
<button @click="openEditModal(service)" class="btn btn-sm btn-primary">View</button>
</td>
</tr>
</tbody>
</table>
</div>
<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>
<!-- MOBILE VIEW: Cards (Revamped) -->
<div class="xl:hidden space-y-4">
<!-- Removed @click from card div -->
<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">
<!-- Moved @click handler to the button -->
<button @click="openEditModal(service)" class="btn btn-sm btn-primary">View</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -84,13 +135,10 @@
@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';
@@ -109,22 +157,47 @@ interface ServiceCall {
export default defineComponent({
name: 'ServiceHPast',
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() {
this.userStatus();
this.fetchUpcomingServices();
this.fetchPastServices();
},
methods: {
// --- NEW METHODS FOR TRUNCATION ---
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);
}
},
// --- API and Data Handling Methods ---
async fetchUpcomingServices(): Promise<void> {
async fetchPastServices(): Promise<void> {
this.isLoading = true;
try {
const path = import.meta.env.VITE_BASE_URL + '/service/past';
@@ -134,7 +207,7 @@ export default defineComponent({
});
this.services = response.data;
} catch (error) {
console.error("Failed to fetch upcoming service calls:", error);
console.error("Failed to fetch past service calls:", error);
} finally {
this.isLoading = false;
}
@@ -198,16 +271,15 @@ export default defineComponent({
},
// --- Formatting and Display Methods ---
formatCurrency(value: string): string {
if (!value) return '$0.00';
const costAsNumber = parseFloat(value);
if (isNaN(costAsNumber)) {
return value;
}
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(costAsNumber);
}).format(numberValue);
},
formatDate(dateString: string): string {

View File

@@ -1,39 +1,57 @@
<template>
<Header />
<div class="flex">
<div class="w-full px-10">
<div class="text-sm breadcrumbs mb-4">
<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><router-link :to="{ name: 'customer' }">Customers</router-link></li>
<li v-if="customer">{{ customer.customer_first_name }} {{ customer.customer_last_name }}</li>
<li>Service Calendar</li>
</ul>
</div>
<div class="flex h-screen font-sans">
<div v-if="isLoading" class="w-1/4 p-4 border-r">
<h2 class="text-xl font-bold">Loading Customer...</h2>
</div>
<EventSidebar v-else-if="customer" :customer="customer" @event-scheduled="handleEventScheduled" />
<div v-else class="w-1/4 p-4 border-r">
<h2 class="text-xl font-bold text-red-500">Error</h2>
<p>Could not load customer data. You can still view the master calendar.</p>
<!--
Main Responsive Container:
- Stacks vertically on mobile (flex-col)
- Sits side-by-side on large screens (lg:flex-row)
-->
<div class="flex flex-col lg:flex-row gap-6 mt-6">
<!-- Sidebar Area (Uses our new responsive EventSidebar) -->
<EventSidebar v-if="!isLoading && customer" :customer="customer" @event-scheduled="handleEventScheduled" />
<!-- Loading/Error States (Styled to match the sidebar) -->
<div v-else class="w-full lg:w-96 lg:flex-none p-4">
<div class="bg-neutral rounded-lg p-6 sticky top-4 text-center">
<div v-if="isLoading">
<span class="loading loading-spinner"></span>
<p class="mt-2">Loading Customer...</p>
</div>
<div v-else>
<h2 class="text-xl font-bold text-error">Error</h2>
<p>Could not load customer data. You can still view the master calendar.</p>
</div>
</div>
</div>
<div class="flex-1 p-4 overflow-auto">
<!-- Main Calendar Area -->
<div class="flex-grow bg-neutral rounded-lg p-4">
<FullCalendar ref="fullCalendar" :options="calendarOptions" />
</div>
<ServiceEditModal
v-if="selectedServiceForEdit"
:service="selectedServiceForEdit"
@close-modal="closeEditModal"
@save-changes="handleSaveChanges"
@delete-service="handleDeleteService"
/>
</div>
</div>
</div>
<!-- Modal remains at the root level, which is correct -->
<ServiceEditModal
v-if="selectedServiceForEdit"
:service="selectedServiceForEdit"
@close-modal="closeEditModal"
@save-changes="handleSaveChanges"
@delete-service="handleDeleteService"
/>
</template>
<script lang="ts">

View File

@@ -1,65 +1,77 @@
<template>
<div class="w-1/4 p-4 border-r">
<h2 class="text-xl font-bold mb-4">Schedule Service</h2>
<form @submit.prevent="submitEvent">
<div class="mb-4">
<!-- CHANGED: Class updated to 'text-gray-200' for visibility on dark backgrounds -->
<label for="event-label" class="block text-sm font-medium text-gray-200">Calendar Label</label>
<input type="text" id="event-label" v-model="event.title" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-black">
</div>
<!--
This container is now responsive. It's full-width on mobile
and becomes a fixed-width sidebar on large screens.
-->
<div class="w-full lg:w-96 lg:flex-none p-4">
<!-- The sidebar is now sticky to the top on large screens -->
<div class="bg-neutral rounded-lg p-6 sticky top-4">
<h2 class="text-xl font-bold mb-4">Schedule Service</h2>
<form @submit.prevent="submitEvent" class="space-y-4">
<!-- Calendar Label -->
<div class="form-control">
<label class="label"><span class="label-text">Calendar Label</span></label>
<input type="text" v-model="event.title" required class="input input-bordered input-sm w-full" placeholder="e.g., Boiler Tune-up">
</div>
<div class="mb-4">
<!-- CHANGED: Class updated to 'text-gray-200' for visibility on dark backgrounds -->
<label for="service_type" class="block text-sm font-medium text-gray-200">Type of Service</label>
<select class="select select-bordered select-sm w-full max-w-xs bg-white text-black" id="service_type" v-model="selectedService" required>
<option disabled value="">Please select one</option>
<option v-for="option in serviceOptions" :key="option.value" :value="option.value">
{{ option.text }}
</option>
</select>
</div>
<!-- Service Type -->
<div class="form-control">
<label class="label"><span class="label-text">Type of Service</span></label>
<select class="select select-bordered select-sm w-full" v-model="selectedService" required>
<option disabled value="">Please select one</option>
<option v-for="option in serviceOptions" :key="option.value" :value="option.value">
{{ option.text }}
</option>
</select>
</div>
<div class="mb-4">
<!-- CHANGED: Class updated to 'text-gray-200' for visibility on dark backgrounds -->
<label for="event-description" class="block text-sm font-medium text-gray-200">Description</label>
<textarea id="event-description" v-model="event.description" rows="3" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-black"></textarea>
</div>
<!-- Description -->
<div class="form-control">
<label class="label"><span class="label-text">Description</span></label>
<textarea v-model="event.description" rows="3" required class="textarea textarea-bordered textarea-sm" placeholder="Notes for the technician..."></textarea>
</div>
<div class="mb-4">
<!-- CHANGED: Class updated to 'text-gray-200' for visibility on dark backgrounds -->
<label for="event-date" class="block text-sm font-medium text-gray-200">Day / Month</label>
<input type="date" id="event-date" v-model="event.date" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-black">
</div>
<!-- Date & Time Grid -->
<div class="grid grid-cols-2 gap-4">
<!-- Date -->
<div class="form-control">
<label class="label"><span class="label-text">Date</span></label>
<input type="date" v-model="event.date" required class="input input-bordered input-sm w-full">
</div>
<!-- Time -->
<div class="form-control">
<label class="label"><span class="label-text">Time</span></label>
<select v-model="event.time" class="select select-bordered select-sm w-full">
<option v-for="hour in 24" :key="hour" :value="hour - 1">{{ (hour - 1).toString().padStart(2, '0') }}:00</option>
</select>
</div>
</div>
<div class="mb-4">
<!-- CHANGED: Class updated to 'text-gray-200' for visibility on dark backgrounds -->
<label for="event-time" class="block text-sm font-medium text-gray-200">Time (Hour)</label>
<select id="event-time" v-model="event.time" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-black">
<option v-for="hour in 24" :key="hour" :value="hour - 1">{{ (hour - 1).toString().padStart(2, '0') }}:00</option>
</select>
</div>
<button type="submit" class="btn btn-primary btn-sm w-full mt-4">
Add Event
</button>
</form>
<button type="submit" class="w-full bg-green-600 text-white py-2 px-4 rounded-md hover:bg-green-700">
Add Event
</button>
</form>
<div v-if="customer" class="mt-10 border-t pt-4">
<div class="font-bold text-lg">
{{ customer.customer_first_name }} {{ customer.customer_last_name }}
<!-- Customer Info Section -->
<div v-if="customer" class="mt-6">
<div class="divider">For Customer</div>
<!-- Customer Info "Card within a Card" -->
<div class="card bg-base-100 shadow-md">
<div class="card-body p-4">
<h3 class="card-title text-base">{{ customer.customer_first_name }} {{ customer.customer_last_name }}</h3>
<div class="text-sm mt-2 space-y-1">
<p>{{ customer.customer_address }}</p>
<p v-if="customer.customer_apt && customer.customer_apt !== 'None'">{{ customer.customer_apt }}</p>
<p>{{ customer.customer_town }}, {{ customerStateName }} {{ customer.customer_zip }}</p>
<p class="pt-2 font-semibold">{{ customer.customer_phone_number }}</p>
</div>
<div class="card-actions justify-end">
<div class="badge badge-outline">{{ customerHomeType }}</div>
</div>
</div>
</div>
</div>
<div>{{ customer.customer_address }}</div>
<div v-if="customer.customer_apt">{{ customer.customer_apt }}</div>
<div>
<span>{{ customer.customer_town }},</span>
<span class="pl-1">{{ customerStateName }}</span>
<span class="pl-1">{{ customer.customer_zip }}</span>
</div>
<div>{{ customer.customer_phone_number }}</div>
<div class="text-sm text-gray-500 mt-2">{{ customerHomeType }}</div>
</div>
</div>
</template>