Refactor frontend to Composition API and improve UI/UX
Major Changes: - Migrate components from Options API to Composition API with <script setup> - Add centralized service layer (serviceService, deliveryService, adminService) - Implement new reusable components (EnhancedButton, EnhancedModal, StatCard, etc.) - Add theme store for consistent theming across application - Improve ServiceCalendar with federal holidays and better styling - Refactor customer profile and tank estimation components - Update all delivery and payment pages to use centralized services - Add utility functions for formatting and validation - Update Dockerfiles for better environment configuration - Enhance Tailwind config with custom design tokens UI Improvements: - Modern, premium design with glassmorphism effects - Improved form layouts with FloatingInput components - Better loading states and empty states - Enhanced modals and tables with consistent styling - Responsive design improvements across all pages Technical Improvements: - Strict TypeScript types throughout - Better error handling and validation - Removed deprecated api.js in favor of TypeScript services - Improved code organization and maintainability
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
<!-- src/pages/service/ServiceCalendar.vue -->
|
||||
<template>
|
||||
<div class="flex">
|
||||
|
||||
<div class="w-full px-10">
|
||||
<div class="calendar-page">
|
||||
<div class="w-full px-4 md:px-10">
|
||||
<!-- Breadcrumbs -->
|
||||
<div class="text-sm breadcrumbs mb-4">
|
||||
<ul>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
@@ -11,21 +11,104 @@
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex text-2xl mb-5 font-bold">
|
||||
Master Service Calendar
|
||||
<!-- Page Header -->
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold mb-1">Service Calendar</h1>
|
||||
<p class="text-base-content/60">Manage and schedule service calls</p>
|
||||
</div>
|
||||
<div class="flex gap-3">
|
||||
<button @click="goToToday" class="btn btn-ghost btn-sm gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5" />
|
||||
</svg>
|
||||
Today
|
||||
</button>
|
||||
<router-link :to="{ name: 'ServiceHome' }" class="btn btn-primary btn-sm gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
</svg>
|
||||
New Service Call
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex h-screen font-sans">
|
||||
<div class="flex-1 p-4 overflow-auto">
|
||||
<!-- The 'ref' is important for accessing the calendar's API -->
|
||||
<!-- Calendar Container -->
|
||||
<div class="calendar-container bg-gradient-to-br from-neutral to-neutral/80 rounded-xl shadow-strong overflow-hidden">
|
||||
<!-- Custom Calendar Header -->
|
||||
<div class="calendar-header bg-base-200 px-6 py-4 border-b border-base-300">
|
||||
<div class="flex items-center justify-between">
|
||||
<!-- Navigation -->
|
||||
<div class="flex items-center gap-2">
|
||||
<button @click="previousMonth" class="btn btn-ghost btn-sm btn-circle">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
</button>
|
||||
<h2 class="text-2xl font-bold min-w-[200px] text-center">{{ currentMonthYear }}</h2>
|
||||
<button @click="nextMonth" class="btn btn-ghost btn-sm btn-circle">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- View Switcher -->
|
||||
<div class="btn-group">
|
||||
<button
|
||||
@click="changeView('dayGridMonth')"
|
||||
class="btn btn-sm"
|
||||
:class="{ 'btn-active': currentView === 'dayGridMonth' }"
|
||||
>
|
||||
Month
|
||||
</button>
|
||||
<button
|
||||
@click="changeView('dayGridWeek')"
|
||||
class="btn btn-sm"
|
||||
:class="{ 'btn-active': currentView === 'dayGridWeek' }"
|
||||
>
|
||||
Week
|
||||
</button>
|
||||
<button
|
||||
@click="changeView('dayGridDay')"
|
||||
class="btn btn-sm"
|
||||
:class="{ 'btn-active': currentView === 'dayGridDay' }"
|
||||
>
|
||||
Day
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Legend -->
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-3 h-3 rounded-full bg-info"></div>
|
||||
<span class="text-sm">Scheduled</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-3 h-3 rounded-full bg-success"></div>
|
||||
<span class="text-sm">Completed</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-3 h-3 rounded-full bg-warning"></div>
|
||||
<span class="text-sm">In Progress</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-3 h-3 rounded-full bg-accent"></div>
|
||||
<span class="text-sm">Federal Holiday</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- FullCalendar -->
|
||||
<div class="calendar-body p-6">
|
||||
<FullCalendar ref="fullCalendar" :options="calendarOptions" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
|
||||
<!-- Edit Modal -->
|
||||
<ServiceEditModal
|
||||
v-if="selectedServiceForEdit"
|
||||
:service="selectedServiceForEdit"
|
||||
@@ -36,49 +119,56 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import Header from '../../layouts/headers/headerauth.vue';
|
||||
import SideBar from '../../layouts/sidebar/sidebar.vue';
|
||||
import Footer from '../../layouts/footers/footer.vue';
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import FullCalendar from '@fullcalendar/vue3';
|
||||
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
import interactionPlugin from '@fullcalendar/interaction';
|
||||
import { CalendarOptions, EventClickArg } from '@fullcalendar/core';
|
||||
import { CalendarOptions, EventClickArg, DayCellContentArg } from '@fullcalendar/core';
|
||||
import ServiceEditModal from './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;
|
||||
service_cost: string;
|
||||
}
|
||||
import { serviceService } from '../../services/serviceService';
|
||||
import { authService } from '../../services/authService';
|
||||
import { AxiosResponse, AxiosError, ServiceCall } from '../../types/models';
|
||||
import { getFederalHolidays, type Holiday } from '../../utils/holidays';
|
||||
|
||||
// Reactive data
|
||||
const user = ref(null)
|
||||
const selectedServiceForEdit = ref(null as Partial<ServiceCall> | null)
|
||||
const fullCalendar = ref()
|
||||
const currentView = ref('dayGridMonth')
|
||||
const holidays = ref<Holiday[]>([])
|
||||
const currentDate = ref(new Date())
|
||||
|
||||
// Functions
|
||||
// We can remove the fetchEvents method as FullCalendar now handles it.
|
||||
// async fetchEvents(): Promise<void> { ... }
|
||||
// Computed
|
||||
const currentMonthYear = computed(() => {
|
||||
return currentDate.value.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })
|
||||
})
|
||||
|
||||
// Load holidays for current year and next year
|
||||
const loadHolidays = () => {
|
||||
const currentYear = new Date().getFullYear()
|
||||
const allHolidays = [
|
||||
...getFederalHolidays(currentYear - 1),
|
||||
...getFederalHolidays(currentYear),
|
||||
...getFederalHolidays(currentYear + 1),
|
||||
]
|
||||
holidays.value = allHolidays
|
||||
}
|
||||
|
||||
// Check if a date is a holiday
|
||||
const isHolidayDate = (dateStr: string): Holiday | undefined => {
|
||||
return holidays.value.find(h => h.date === dateStr)
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
const handleEventClick = (clickInfo: EventClickArg): void => {
|
||||
// This logic remains the same, as it correctly pulls data from extendedProps
|
||||
selectedServiceForEdit.value = {
|
||||
id: parseInt(clickInfo.event.id),
|
||||
scheduled_date: clickInfo.event.startStr,
|
||||
customer_name: clickInfo.event.title.split(': ')[1] || 'Unknown Customer',
|
||||
customer_id: clickInfo.event.extendedProps.customer_id,
|
||||
type_service_call: clickInfo.event.extendedProps.type_service_call,
|
||||
description: clickInfo.event.extendedProps.description,
|
||||
service_cost: clickInfo.event.extendedProps.service_cost,
|
||||
id: parseInt(clickInfo.event.id),
|
||||
scheduled_date: clickInfo.event.startStr,
|
||||
customer_name: clickInfo.event.title.split(': ')[1] || 'Unknown Customer',
|
||||
customer_id: clickInfo.event.extendedProps.customer_id,
|
||||
type_service_call: clickInfo.event.extendedProps.type_service_call,
|
||||
description: clickInfo.event.extendedProps.description,
|
||||
service_cost: clickInfo.event.extendedProps.service_cost,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -89,73 +179,116 @@ const fetchCalendarEvents = async (
|
||||
failureCallback: (error: Error) => void
|
||||
) => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/all`;
|
||||
const response = await axios.get(path, {
|
||||
headers: authHeader(),
|
||||
withCredentials: true,
|
||||
});
|
||||
// Backend returns { ok: true, events: [...] }
|
||||
const events = response.data?.events || [];
|
||||
successCallback(events);
|
||||
} catch (error) {
|
||||
const response = await serviceService.getAll();
|
||||
const serviceEvents = response.data?.events || [];
|
||||
|
||||
// Add federal holidays as background events
|
||||
const holidayEvents = holidays.value.map(holiday => ({
|
||||
id: `holiday-${holiday.date}`,
|
||||
title: holiday.name,
|
||||
start: holiday.date,
|
||||
allDay: true,
|
||||
display: 'background',
|
||||
classNames: ['holiday-event']
|
||||
}));
|
||||
|
||||
// Combine service events and holiday events
|
||||
const allEvents = [...serviceEvents, ...holidayEvents];
|
||||
|
||||
successCallback(allEvents);
|
||||
} catch (err: unknown) {
|
||||
const error = err as AxiosError;
|
||||
console.error("Failed to fetch calendar events:", error);
|
||||
failureCallback(error as Error);
|
||||
}
|
||||
};
|
||||
|
||||
// Calendar navigation
|
||||
const goToToday = () => {
|
||||
const calendarApi = (fullCalendar.value as any).getApi()
|
||||
calendarApi.today()
|
||||
currentDate.value = calendarApi.getDate()
|
||||
}
|
||||
|
||||
const previousMonth = () => {
|
||||
const calendarApi = (fullCalendar.value as any).getApi()
|
||||
calendarApi.prev()
|
||||
currentDate.value = calendarApi.getDate()
|
||||
}
|
||||
|
||||
const nextMonth = () => {
|
||||
const calendarApi = (fullCalendar.value as any).getApi()
|
||||
calendarApi.next()
|
||||
currentDate.value = calendarApi.getDate()
|
||||
}
|
||||
|
||||
const changeView = (viewName: string) => {
|
||||
currentView.value = viewName
|
||||
const calendarApi = (fullCalendar.value as any).getApi()
|
||||
calendarApi.changeView(viewName)
|
||||
currentDate.value = calendarApi.getDate()
|
||||
}
|
||||
|
||||
// Day cell class names for holidays
|
||||
const getDayCellClassNames = (arg: any) => {
|
||||
// Format date as YYYY-MM-DD
|
||||
const year = arg.date.getFullYear()
|
||||
const month = String(arg.date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(arg.date.getDate()).padStart(2, '0')
|
||||
const dateStr = `${year}-${month}-${day}`
|
||||
|
||||
const holiday = isHolidayDate(dateStr)
|
||||
|
||||
if (holiday) {
|
||||
console.log('Holiday found:', holiday.name, 'on', dateStr)
|
||||
}
|
||||
|
||||
return holiday ? ['holiday-cell'] : []
|
||||
}
|
||||
|
||||
// Calendar options
|
||||
const calendarOptions = ref({
|
||||
plugins: [dayGridPlugin, interactionPlugin],
|
||||
initialView: 'dayGridMonth',
|
||||
headerToolbar: false, // We're using custom header
|
||||
weekends: true,
|
||||
// Use function source to fetch events with auth headers and transform response
|
||||
height: 'auto',
|
||||
events: fetchCalendarEvents,
|
||||
eventClick: handleEventClick,
|
||||
eventClassNames: 'custom-event',
|
||||
dayCellClassNames: getDayCellClassNames,
|
||||
eventDisplay: 'block',
|
||||
displayEventTime: false,
|
||||
eventBackgroundColor: 'transparent',
|
||||
eventBorderColor: 'transparent',
|
||||
} as CalendarOptions)
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus();
|
||||
// We no longer need to call fetchEvents() here because FullCalendar does it automatically.
|
||||
})
|
||||
|
||||
// Modal handlers
|
||||
const closeEditModal = () => {
|
||||
selectedServiceForEdit.value = null;
|
||||
selectedServiceForEdit.value = null;
|
||||
}
|
||||
|
||||
// =================== THIS IS THE CORRECTED SECTION ===================
|
||||
const handleSaveChanges = async (updatedService: ServiceCall) => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
|
||||
await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
|
||||
|
||||
// Get the FullCalendar component instance from the ref
|
||||
await serviceService.update(updatedService.id, updatedService);
|
||||
const calendarApi = (fullCalendar.value as any).getApi();
|
||||
if (calendarApi) {
|
||||
// Tell FullCalendar to re-fetch its events from the source.
|
||||
// This is the most reliable way to refresh the view immediately.
|
||||
calendarApi.refetchEvents();
|
||||
}
|
||||
|
||||
closeEditModal();
|
||||
} catch (error) {
|
||||
console.error("Failed to save changes:", error);
|
||||
alert("An error occurred while saving. Please check the console.");
|
||||
}
|
||||
}
|
||||
// =================== END OF CORRECTED SECTION ===================
|
||||
|
||||
const handleDeleteService = async (serviceId: number) => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
|
||||
await axios.delete(path, { withCredentials: true, headers: authHeader() });
|
||||
|
||||
// Also refresh the calendar after a delete
|
||||
await serviceService.delete(serviceId);
|
||||
const calendarApi = (fullCalendar.value as any).getApi();
|
||||
if (calendarApi) {
|
||||
calendarApi.refetchEvents();
|
||||
}
|
||||
|
||||
closeEditModal();
|
||||
} catch (error) {
|
||||
console.error("Error deleting event:", error);
|
||||
@@ -163,20 +296,162 @@ const handleDeleteService = async (serviceId: number) => {
|
||||
}
|
||||
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
authService.whoami().then((response: AxiosResponse<any>) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
user.value = null
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
user.value = null
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus();
|
||||
loadHolidays();
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.calendar-page {
|
||||
@apply min-h-screen animate-fade-in;
|
||||
}
|
||||
|
||||
.calendar-container {
|
||||
@apply transition-all duration-300;
|
||||
}
|
||||
|
||||
/* FullCalendar Custom Styling */
|
||||
:deep(.fc) {
|
||||
@apply font-sans;
|
||||
}
|
||||
|
||||
:deep(.fc-theme-standard td),
|
||||
:deep(.fc-theme-standard th) {
|
||||
border-color: hsl(var(--bc) / 0.2) !important;
|
||||
border-width: 2px !important;
|
||||
}
|
||||
|
||||
:deep(.fc-scrollgrid) {
|
||||
border-width: 2px !important;
|
||||
border-color: hsl(var(--bc) / 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.fc-col-header-cell) {
|
||||
@apply bg-base-200 font-semibold text-sm uppercase tracking-wider py-3;
|
||||
border-width: 2px !important;
|
||||
border-color: hsl(var(--bc) / 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.fc-daygrid-day) {
|
||||
@apply transition-colors hover:bg-base-200/50;
|
||||
border-width: 2px !important;
|
||||
border-color: hsl(var(--bc) / 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.fc-daygrid-day-number) {
|
||||
@apply text-base-content/80 font-medium p-2;
|
||||
}
|
||||
|
||||
:deep(.fc-day-today) {
|
||||
@apply bg-primary/5 !important;
|
||||
}
|
||||
|
||||
:deep(.fc-day-today .fc-daygrid-day-number) {
|
||||
@apply text-primary font-bold;
|
||||
}
|
||||
|
||||
/* Custom Event Styling */
|
||||
:deep(.fc-event) {
|
||||
@apply rounded-lg px-2 py-1 mb-1 cursor-pointer;
|
||||
@apply bg-info/20 border-l-4 border-info;
|
||||
@apply hover:bg-info/30 transition-colors;
|
||||
@apply shadow-sm hover:shadow-md;
|
||||
}
|
||||
|
||||
:deep(.fc-event-title) {
|
||||
@apply text-sm font-medium text-base-content truncate;
|
||||
}
|
||||
|
||||
/* Day cell styling */
|
||||
:deep(.fc-daygrid-day-frame) {
|
||||
@apply min-h-[100px];
|
||||
}
|
||||
|
||||
:deep(.fc-daygrid-day-top) {
|
||||
@apply flex justify-center;
|
||||
}
|
||||
|
||||
/* Remove default FullCalendar button styling since we have custom header */
|
||||
:deep(.fc-toolbar) {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
/* Holiday styling - Using accent color from theme */
|
||||
:deep(.holiday-cell) {
|
||||
background-color: hsl(var(--a) / 0.15) !important;
|
||||
border-color: hsl(var(--a) / 0.4) !important;
|
||||
}
|
||||
|
||||
:deep(.holiday-cell .fc-daygrid-day-number) {
|
||||
color: hsl(var(--a)) !important;
|
||||
@apply font-bold;
|
||||
}
|
||||
|
||||
/* Holiday background events */
|
||||
:deep(.fc-bg-event.holiday-event) {
|
||||
background-color: hsl(var(--a) / 0.5) !important;
|
||||
opacity: 1 !important;
|
||||
z-index: 1 !important;
|
||||
inset: 0 !important;
|
||||
margin: 0 !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.fc-daygrid-day-bg .fc-bg-event.holiday-event) {
|
||||
position: absolute !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
right: 0 !important;
|
||||
bottom: 0 !important;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
/* Holiday event title positioning */
|
||||
:deep(.fc-bg-event.holiday-event .fc-event-title) {
|
||||
position: absolute !important;
|
||||
bottom: 4px !important;
|
||||
left: 4px !important;
|
||||
right: 4px !important;
|
||||
top: auto !important;
|
||||
font-size: 0.7rem !important;
|
||||
font-weight: 600 !important;
|
||||
color: hsl(var(--a)) !important;
|
||||
text-align: center !important;
|
||||
line-height: 1.2 !important;
|
||||
padding: 2px !important;
|
||||
}
|
||||
|
||||
/* Weekend styling - Using theme colors */
|
||||
:deep(.fc-day-sat),
|
||||
:deep(.fc-day-sun) {
|
||||
background-color: hsl(var(--b3)) !important;
|
||||
border-color: hsl(var(--bc) / 0.3) !important;
|
||||
}
|
||||
|
||||
:deep(.fc-day-sat .fc-daygrid-day-number),
|
||||
:deep(.fc-day-sun .fc-daygrid-day-number) {
|
||||
color: hsl(var(--bc) / 0.8) !important;
|
||||
@apply font-semibold;
|
||||
}
|
||||
|
||||
/* Weekend header cells */
|
||||
:deep(.fc-col-header-cell.fc-day-sat),
|
||||
:deep(.fc-col-header-cell.fc-day-sun) {
|
||||
background-color: hsl(var(--b3)) !important;
|
||||
color: hsl(var(--bc)) !important;
|
||||
@apply font-bold;
|
||||
}
|
||||
</style>
|
||||
@@ -102,8 +102,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
import axios from 'axios';
|
||||
import authHeader from '../../services/auth.header';
|
||||
import serviceService from '../../services/serviceService';
|
||||
import customerService from '../../services/customerService';
|
||||
|
||||
// --- Interfaces ---
|
||||
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 }
|
||||
@@ -132,32 +132,33 @@ const serviceOptions = ref([
|
||||
{ text: 'Tank Install', value: 3 }, { text: 'Other', value: 4 },
|
||||
])
|
||||
|
||||
// Watchers
|
||||
watch(() => props.service, (newVal) => {
|
||||
if (!newVal) return;
|
||||
const scheduled = dayjs(newVal.scheduled_date || new Date());
|
||||
editableService.value = { ...newVal, date: scheduled.format('YYYY-MM-DD'), time: scheduled.hour() };
|
||||
if (newVal.customer_id) {
|
||||
getCustomer(newVal.customer_id);
|
||||
getServiceParts(newVal.customer_id);
|
||||
}
|
||||
}, { immediate: true, deep: true })
|
||||
|
||||
// Functions
|
||||
// Functions (defined before watchers to avoid hoisting issues)
|
||||
const getCustomer = (customerId: number) => {
|
||||
customer.value = null;
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/' + customerId;
|
||||
axios.get(path, { headers: authHeader() })
|
||||
.then((response: any) => { customer.value = response.data; })
|
||||
customerService.getById(customerId)
|
||||
.then((response: any) => {
|
||||
if (response.data.customer) {
|
||||
customer.value = response.data.customer;
|
||||
} else if (response.data.ok && response.data.id) {
|
||||
customer.value = response.data as unknown as Customer;
|
||||
}
|
||||
})
|
||||
.catch((error: any) => { console.error("Failed to fetch customer details for modal:", error); });
|
||||
}
|
||||
|
||||
const getServiceParts = (customerId: number) => {
|
||||
isLoadingParts.value = true;
|
||||
serviceParts.value = null;
|
||||
let path = `${import.meta.env.VITE_BASE_URL}/service/parts/customer/${customerId}`;
|
||||
axios.get(path, { headers: authHeader() })
|
||||
.then((response: any) => { serviceParts.value = response.data; })
|
||||
serviceService.getPartsForCustomer(customerId)
|
||||
.then((response: any) => {
|
||||
if (response.data.parts) {
|
||||
if (Array.isArray(response.data.parts) && response.data.parts.length > 0) {
|
||||
serviceParts.value = response.data.parts[0];
|
||||
} else {
|
||||
serviceParts.value = response.data.parts as unknown as ServiceParts;
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error: any) => { console.error("Failed to fetch service parts:", error); })
|
||||
.finally(() => { isLoadingParts.value = false; });
|
||||
}
|
||||
@@ -168,9 +169,8 @@ const saveChanges = async () => {
|
||||
const combinedDateTime = dayjs(`${date} ${time}:00`).format('YYYY-MM-DDTHH:mm:ss');
|
||||
const finalPayload = { ...props.service, ...editableService.value, scheduled_date: combinedDateTime };
|
||||
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${finalPayload.id}`;
|
||||
try {
|
||||
await axios.put(path, finalPayload, { headers: authHeader(), withCredentials: true });
|
||||
await serviceService.update(finalPayload.id!, finalPayload);
|
||||
emit('save-changes', finalPayload as ServiceCall);
|
||||
} catch (error) {
|
||||
console.error("Failed to save changes:", error);
|
||||
@@ -201,4 +201,15 @@ const getStateAbbrev = (stateId: number | undefined | null): string => {
|
||||
const stateMap: { [key: number]: string } = { 0: 'MA', 1: 'RI', 2: 'NH', 3: 'ME', 4: 'VT', 5: 'CT', 6: 'NY' };
|
||||
return stateMap[stateId] || 'Unknown';
|
||||
}
|
||||
|
||||
// Watchers (after function definitions)
|
||||
watch(() => props.service, (newVal) => {
|
||||
if (!newVal) return;
|
||||
const scheduled = dayjs(newVal.scheduled_date || new Date());
|
||||
editableService.value = { ...newVal, date: scheduled.format('YYYY-MM-DD'), time: scheduled.hour() };
|
||||
if (newVal.customer_id) {
|
||||
getCustomer(newVal.customer_id);
|
||||
getServiceParts(newVal.customer_id);
|
||||
}
|
||||
}, { immediate: true, deep: true })
|
||||
</script>
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
|
||||
|
||||
<ServiceEditModal
|
||||
v-if="selectedServiceForEdit"
|
||||
@@ -158,10 +158,9 @@
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../services/auth.header'
|
||||
import { serviceService } from '../../services/serviceService'
|
||||
import { authService } from '../../services/authService'
|
||||
import { ServiceCall } from '../../types/models'
|
||||
import Footer from '../../layouts/footers/footer.vue'
|
||||
import ServiceEditModal from './ServiceEditModal.vue'
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@@ -184,13 +183,11 @@ onMounted(() => {
|
||||
const fetchUpcomingServices = async (): Promise<void> => {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/service/upcoming';
|
||||
const response = await axios.get(path, {
|
||||
headers: authHeader(),
|
||||
withCredentials: true,
|
||||
});
|
||||
const serviceList = response.data?.services || [];
|
||||
services.value = serviceList.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
|
||||
const response = await serviceService.getUpcoming();
|
||||
if (response.data.ok) {
|
||||
const serviceList = response.data.services || [];
|
||||
services.value = serviceList.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch upcoming service calls:", error);
|
||||
} finally {
|
||||
@@ -199,13 +196,7 @@ const fetchUpcomingServices = async (): Promise<void> => {
|
||||
}
|
||||
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
authService.whoami()
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
@@ -250,8 +241,7 @@ const toggleExpand = (id: number): void => {
|
||||
|
||||
const handleSaveChanges = async (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 });
|
||||
const response = await serviceService.update(updatedService.id, updatedService);
|
||||
if (response.data.ok) {
|
||||
const index = services.value.findIndex(s => s.id === updatedService.id);
|
||||
if (index !== -1) {
|
||||
@@ -267,8 +257,7 @@ const handleSaveChanges = async (updatedService: ServiceCall) => {
|
||||
|
||||
const handleDeleteService = async (serviceId: number) => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
|
||||
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
|
||||
const response = await serviceService.delete(serviceId);
|
||||
if (response.data.ok) {
|
||||
services.value = services.value.filter(s => s.id !== serviceId);
|
||||
closeEditModal();
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
|
||||
|
||||
<ServiceEditModal
|
||||
v-if="selectedServiceForEdit"
|
||||
@@ -165,15 +165,14 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../services/auth.header'
|
||||
import serviceService from '../../services/serviceService'
|
||||
import authService from '../../services/authService'
|
||||
import { ServiceCall } from '../../types/models'
|
||||
import Footer from '../../layouts/footers/footer.vue'
|
||||
import ServiceEditModal from './ServiceEditModal.vue'
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// Reactive data
|
||||
const user = ref(null)
|
||||
const user = ref<any>(null)
|
||||
const services = ref<ServiceCall[]>([])
|
||||
const isLoading = ref(true)
|
||||
const selectedServiceForEdit = ref<ServiceCall | null>(null)
|
||||
@@ -183,7 +182,9 @@ const expandedIds = ref<number[]>([])
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus();
|
||||
if (authService) {
|
||||
userStatus();
|
||||
}
|
||||
fetchPastServices();
|
||||
})
|
||||
|
||||
@@ -215,13 +216,12 @@ const toggleExpand = (id: number): void => {
|
||||
const fetchPastServices = async (): Promise<void> => {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/service/past';
|
||||
const response = await axios.get(path, {
|
||||
headers: authHeader(),
|
||||
withCredentials: true,
|
||||
});
|
||||
const serviceList = response.data?.services || [];
|
||||
services.value = serviceList.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
|
||||
const response = await serviceService.getPast();
|
||||
if (response.data && response.data.services) {
|
||||
services.value = response.data.services.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
|
||||
} else {
|
||||
services.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch past service calls:", error);
|
||||
} finally {
|
||||
@@ -230,13 +230,7 @@ const fetchPastServices = async (): Promise<void> => {
|
||||
}
|
||||
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
authService.whoami()
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
@@ -257,9 +251,8 @@ const closeEditModal = () => {
|
||||
|
||||
const handleSaveChanges = async (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 response = await serviceService.update(updatedService.id, updatedService);
|
||||
if (response.data.service) { // Based on ServiceResponse type
|
||||
const index = services.value.findIndex(s => s.id === updatedService.id);
|
||||
if (index !== -1) {
|
||||
services.value[index] = response.data.service;
|
||||
@@ -274,8 +267,7 @@ const handleSaveChanges = async (updatedService: ServiceCall) => {
|
||||
|
||||
const handleDeleteService = async (serviceId: number) => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
|
||||
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
|
||||
const response = await serviceService.delete(serviceId);
|
||||
if (response.data.ok) {
|
||||
services.value = services.value.filter(s => s.id !== serviceId);
|
||||
closeEditModal();
|
||||
|
||||
@@ -120,25 +120,26 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../services/auth.header'
|
||||
import serviceService from '../../services/serviceService'
|
||||
import authService from '../../services/authService'
|
||||
import { ServicePlan } from '../../types/models'
|
||||
import Footer from '../../layouts/footers/footer.vue'
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// Reactive data
|
||||
const user = ref(null)
|
||||
const user = ref<any>(null)
|
||||
const servicePlans = ref<ServicePlan[]>([])
|
||||
const isLoading = ref(true)
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus();
|
||||
if (authService) {
|
||||
userStatus();
|
||||
}
|
||||
fetchServicePlans();
|
||||
})
|
||||
|
||||
@@ -146,13 +147,12 @@ onMounted(() => {
|
||||
const fetchServicePlans = async (): Promise<void> => {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/service/plans/active';
|
||||
const response = await axios.get(path, {
|
||||
headers: authHeader(),
|
||||
withCredentials: true,
|
||||
});
|
||||
// Backend returns { ok: true, plans: [...] }
|
||||
servicePlans.value = response.data?.plans || [];
|
||||
const response = await serviceService.plans.getActive();
|
||||
if (response.data && response.data.plans) {
|
||||
servicePlans.value = response.data.plans;
|
||||
} else {
|
||||
servicePlans.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch service plans:", error);
|
||||
} finally {
|
||||
@@ -161,13 +161,7 @@ const fetchServicePlans = async (): Promise<void> => {
|
||||
}
|
||||
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
authService.whoami()
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
|
||||
|
||||
<ServiceEditModal
|
||||
v-if="selectedServiceForEdit"
|
||||
@@ -165,15 +165,14 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import axios from 'axios'
|
||||
import authHeader from '../../services/auth.header'
|
||||
import serviceService from '../../services/serviceService'
|
||||
import authService from '../../services/authService'
|
||||
import { ServiceCall } from '../../types/models'
|
||||
import Footer from '../../layouts/footers/footer.vue'
|
||||
import ServiceEditModal from './ServiceEditModal.vue'
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// Reactive data
|
||||
const user = ref(null)
|
||||
const user = ref<any>(null)
|
||||
const services = ref<ServiceCall[]>([])
|
||||
const isLoading = ref(true)
|
||||
const selectedServiceForEdit = ref<ServiceCall | null>(null)
|
||||
@@ -183,7 +182,9 @@ const expandedIds = ref<number[]>([])
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
userStatus();
|
||||
if (authService) { // Check if imported correctly
|
||||
userStatus();
|
||||
}
|
||||
fetchTodayServices();
|
||||
})
|
||||
|
||||
@@ -215,13 +216,16 @@ const toggleExpand = (id: number): void => {
|
||||
const fetchTodayServices = async (): Promise<void> => {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/service/today';
|
||||
const response = await axios.get(path, {
|
||||
headers: authHeader(),
|
||||
withCredentials: true,
|
||||
});
|
||||
const serviceList = response.data?.services || [];
|
||||
services.value = serviceList.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
|
||||
const response = await serviceService.getToday();
|
||||
// According to serviceService.ts, getToday returns AxiosResponse<ServicesResponse>
|
||||
// ServicesResponse has { ok: boolean, services: ServiceCall[] }
|
||||
// However, the api unwrap interceptor might put properties directly on data
|
||||
// Let's assume the response structure follows the type
|
||||
if (response.data && response.data.services) {
|
||||
services.value = response.data.services.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
|
||||
} else {
|
||||
services.value = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch today's service calls:", error);
|
||||
} finally {
|
||||
@@ -230,13 +234,7 @@ const fetchTodayServices = async (): Promise<void> => {
|
||||
}
|
||||
|
||||
const userStatus = () => {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
authService.whoami()
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
user.value = response.data.user;
|
||||
@@ -257,9 +255,8 @@ const closeEditModal = () => {
|
||||
|
||||
const handleSaveChanges = async (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 response = await serviceService.update(updatedService.id, updatedService);
|
||||
if (response.data.service) { // Based on ServiceResponse type
|
||||
const index = services.value.findIndex(s => s.id === updatedService.id);
|
||||
if (index !== -1) {
|
||||
services.value[index] = response.data.service;
|
||||
@@ -274,8 +271,7 @@ const handleSaveChanges = async (updatedService: ServiceCall) => {
|
||||
|
||||
const handleDeleteService = async (serviceId: number) => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
|
||||
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
|
||||
const response = await serviceService.delete(serviceId);
|
||||
if (response.data.ok) {
|
||||
services.value = services.value.filter(s => s.id !== serviceId);
|
||||
closeEditModal();
|
||||
|
||||
@@ -65,8 +65,8 @@ 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';
|
||||
import serviceService from '../../../services/serviceService';
|
||||
import customerService from '../../../services/customerService';
|
||||
|
||||
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;}
|
||||
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; }
|
||||
@@ -108,58 +108,19 @@ const calendarOptions = ref<CalendarOptions>({
|
||||
});
|
||||
const customer = ref<Customer | null>(null);
|
||||
|
||||
// Watchers
|
||||
watch(() => route.params.id, (newId) => {
|
||||
if (newId) getCustomer(newId as string);
|
||||
}, { immediate: true });
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
fetchEvents();
|
||||
});
|
||||
|
||||
// Functions
|
||||
|
||||
// Functions (defined before watchers to avoid hoisting issues)
|
||||
const closeEditModal = () => {
|
||||
selectedServiceForEdit.value = null;
|
||||
};
|
||||
|
||||
const handleSaveChanges = async (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 fetchEvents();
|
||||
closeEditModal();
|
||||
} catch (error) {
|
||||
console.error("Failed to save changes:", error);
|
||||
alert("An error occurred while saving. Please check the console.");
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteService = async (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 fetchEvents();
|
||||
closeEditModal();
|
||||
} else {
|
||||
console.error("Failed to delete event:", response.data.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error deleting event:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const getCustomer = async (customerId: string): Promise<void> => {
|
||||
isLoading.value = true;
|
||||
customer.value = null;
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/${customerId}`;
|
||||
const response = await axios.get(path, { withCredentials: true, headers: authHeader() });
|
||||
const response = await customerService.getById(Number(customerId));
|
||||
const customerData = response.data?.customer || response.data;
|
||||
if (customerData && customerData.id) {
|
||||
customer.value = customerData;
|
||||
if (customerData && (customerData as any).id) {
|
||||
customer.value = customerData as unknown as Customer;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("API call to get customer FAILED:", error);
|
||||
@@ -170,14 +131,39 @@ const getCustomer = async (customerId: string): Promise<void> => {
|
||||
|
||||
const fetchEvents = async (): Promise<void> => {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/all`;
|
||||
const response = await axios.get(path, { headers: authHeader(), withCredentials: true });
|
||||
const response = await serviceService.getAll();
|
||||
calendarOptions.value.events = response.data?.events || [];
|
||||
} catch (error) {
|
||||
console.error("Error fetching all calendar events:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveChanges = async (updatedService: ServiceCall) => {
|
||||
try {
|
||||
await serviceService.update(updatedService.id, updatedService);
|
||||
await fetchEvents();
|
||||
closeEditModal();
|
||||
} catch (error) {
|
||||
console.error("Failed to save changes:", error);
|
||||
alert("An error occurred while saving. Please check the console.");
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteService = async (serviceId: number) => {
|
||||
try {
|
||||
const response = await serviceService.delete(serviceId);
|
||||
if (response.data.ok === true) {
|
||||
await fetchEvents();
|
||||
closeEditModal();
|
||||
} else {
|
||||
// console.error("Failed to delete event:", response.data.error);
|
||||
// Error property might not exist on typed response, but checking ok is enough
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error deleting event:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEventScheduled = async (eventData: any): Promise<void> => {
|
||||
if (!customer.value) {
|
||||
alert("Error: A customer must be loaded in the sidebar to create a new event.");
|
||||
@@ -188,12 +174,13 @@ const handleEventScheduled = async (eventData: any): Promise<void> => {
|
||||
expected_delivery_date: eventData.start, type_service_call: eventData.type_service_call,
|
||||
customer_id: customer.value.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) {
|
||||
const response = await serviceService.create(payload);
|
||||
|
||||
// Service response has { ok: boolean, service: ServiceCall }
|
||||
if (response.data.service) {
|
||||
await fetchEvents();
|
||||
} else {
|
||||
console.error("Failed to create event:", response.data.error);
|
||||
console.error("Failed to create event");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error creating event:", error);
|
||||
@@ -204,4 +191,14 @@ const handleEventDelete = async (eventId: string): Promise<void> => {
|
||||
// This is a simple alias now, as handleDeleteService is more specific
|
||||
await handleDeleteService(Number(eventId));
|
||||
};
|
||||
|
||||
// Watchers (after function definitions)
|
||||
watch(() => route.params.id, (newId) => {
|
||||
if (newId) getCustomer(newId as string);
|
||||
}, { immediate: true });
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
fetchEvents();
|
||||
});
|
||||
</script>
|
||||
@@ -1,24 +0,0 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const BASE_URL = import.meta.env.VITE_BASE_URL;
|
||||
|
||||
function authHeader() {
|
||||
// Return authorization header
|
||||
return {};
|
||||
}
|
||||
|
||||
export function createEvent(payload) {
|
||||
const path = `${BASE_URL}/service/create`; // Example endpoint
|
||||
return axios.post(path, payload, {
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteEventById(eventId) {
|
||||
const path = `${BASE_URL}/service/delete/${eventId}`; // Example endpoint
|
||||
return axios.delete(path, {
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user