190 lines
7.2 KiB
Vue
190 lines
7.2 KiB
Vue
<template>
|
|
<Header />
|
|
<div class="flex">
|
|
<div class="w-full px-10">
|
|
<div class="text-sm breadcrumbs mb-4">
|
|
<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>
|
|
</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>
|
|
</div>
|
|
|
|
<div class="flex-1 p-4 overflow-auto">
|
|
<FullCalendar ref="fullCalendar" :options="calendarOptions" />
|
|
</div>
|
|
|
|
<ServiceEditModal
|
|
v-if="selectedServiceForEdit"
|
|
:service="selectedServiceForEdit"
|
|
@close-modal="closeEditModal"
|
|
@save-changes="handleSaveChanges"
|
|
@delete-service="handleDeleteService"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { defineComponent } from 'vue';
|
|
import Header from '../../../layouts/headers/headerauth.vue';
|
|
import FullCalendar from '@fullcalendar/vue3';
|
|
import dayGridPlugin from '@fullcalendar/daygrid';
|
|
import interactionPlugin from '@fullcalendar/interaction';
|
|
import { CalendarOptions, EventClickArg } from '@fullcalendar/core';
|
|
import EventSidebar from './EventSidebar.vue';
|
|
import ServiceEditModal from '../../service/ServiceEditModal.vue';
|
|
import axios from 'axios';
|
|
import authHeader from '../../../services/auth.header';
|
|
|
|
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; }
|
|
interface Customer { id: number; customer_last_name: string; customer_first_name: string; customer_town: string; customer_state: number; customer_zip: string; customer_phone_number: string; customer_address: string; customer_home_type: number; customer_apt: string; }
|
|
|
|
export default defineComponent({
|
|
name: 'CalendarCustomer',
|
|
components: { Header, FullCalendar, EventSidebar, ServiceEditModal },
|
|
data() {
|
|
return {
|
|
isLoading: false,
|
|
selectedServiceForEdit: null as Partial<ServiceCall> | null,
|
|
calendarOptions: {
|
|
plugins: [dayGridPlugin, interactionPlugin],
|
|
initialView: 'dayGridMonth',
|
|
weekends: true,
|
|
events: [] as any[],
|
|
eventClick: this.handleEventClick,
|
|
} as CalendarOptions,
|
|
customer: null as Customer | null,
|
|
};
|
|
},
|
|
watch: {
|
|
'$route.params.id': {
|
|
handler(newId) {
|
|
if (newId) this.getCustomer(newId as string);
|
|
},
|
|
immediate: true,
|
|
},
|
|
},
|
|
created() {
|
|
this.fetchEvents();
|
|
},
|
|
methods: {
|
|
// --- THIS IS THE FIX ---
|
|
// The logic from ServiceCalendar.vue is now correctly applied here.
|
|
handleEventClick(clickInfo: EventClickArg): void {
|
|
const events = (this.calendarOptions.events as any[]) || [];
|
|
const originalEvent = events.find(e => e.id == clickInfo.event.id);
|
|
|
|
if (originalEvent) {
|
|
// We "flatten" the nested object from the calendar into the simple,
|
|
// flat structure that the modal expects, ensuring customer_id is included.
|
|
this.selectedServiceForEdit = {
|
|
id: originalEvent.id,
|
|
scheduled_date: originalEvent.start,
|
|
customer_id: originalEvent.customer_id, // This was the missing piece
|
|
customer_name: originalEvent.title.split(': ')[1] || 'Unknown Customer',
|
|
type_service_call: originalEvent.extendedProps.type_service_call,
|
|
description: originalEvent.extendedProps.description,
|
|
customer_address: '',
|
|
customer_town: '',
|
|
};
|
|
}
|
|
},
|
|
|
|
closeEditModal() {
|
|
this.selectedServiceForEdit = null;
|
|
},
|
|
|
|
async handleSaveChanges(updatedService: ServiceCall) {
|
|
try {
|
|
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
|
|
await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
|
|
await this.fetchEvents();
|
|
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, { withCredentials: true, headers: authHeader() });
|
|
if (response.data.ok === true) {
|
|
await this.fetchEvents();
|
|
this.closeEditModal();
|
|
} else {
|
|
console.error("Failed to delete event:", response.data.error);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error deleting event:", error);
|
|
}
|
|
},
|
|
|
|
async getCustomer(customerId: string): Promise<void> {
|
|
this.isLoading = true;
|
|
this.customer = null;
|
|
try {
|
|
const path = `${import.meta.env.VITE_BASE_URL}/customer/${customerId}`;
|
|
const response = await axios.get(path, { withCredentials: true, headers: authHeader() });
|
|
if (response.data && response.data.id) {
|
|
this.customer = response.data;
|
|
}
|
|
} catch (error) {
|
|
console.error("API call to get customer FAILED:", error);
|
|
} finally {
|
|
this.isLoading = false;
|
|
}
|
|
},
|
|
|
|
async fetchEvents(): Promise<void> {
|
|
try {
|
|
const path = `${import.meta.env.VITE_BASE_URL}/service/all`;
|
|
const response = await axios.get(path, { headers: authHeader(), withCredentials: true });
|
|
this.calendarOptions.events = response.data;
|
|
} catch (error) {
|
|
console.error("Error fetching all calendar events:", error);
|
|
}
|
|
},
|
|
|
|
async handleEventScheduled(eventData: any): Promise<void> {
|
|
if (!this.customer) {
|
|
alert("Error: A customer must be loaded in the sidebar to create a new event.");
|
|
return;
|
|
}
|
|
try {
|
|
const payload = {
|
|
expected_delivery_date: eventData.start, type_service_call: eventData.type_service_call,
|
|
customer_id: this.customer.id, description: eventData.extendedProps.description,
|
|
};
|
|
const path = import.meta.env.VITE_BASE_URL + "/service/create";
|
|
const response = await axios.post(path, payload, { withCredentials: true, headers: authHeader() });
|
|
if (response.data.ok === true) {
|
|
await this.fetchEvents();
|
|
} else {
|
|
console.error("Failed to create event:", response.data.error);
|
|
}
|
|
} catch (error) {
|
|
console.error("Error creating event:", error);
|
|
}
|
|
},
|
|
|
|
async handleEventDelete(eventId: string): Promise<void> {
|
|
// This is a simple alias now, as handleDeleteService is more specific
|
|
await this.handleDeleteService(Number(eventId));
|
|
},
|
|
},
|
|
});
|
|
</script> |