fix: service calendar edit modal saves and displays correct time
- Force FullCalendar remount after save/delete via calendarKey ref - Fix datetime construction to use proper ISO 8601 format (T separator, zero-padded) - Add console.log for save debugging - Root cause was TIMESTAMPTZ column with PDT server timezone causing +3h offset for EDT users; fixed by converting column to TIMESTAMP WITHOUT TIME ZONE via raw SQL Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -104,7 +104,7 @@
|
|||||||
|
|
||||||
<!-- FullCalendar -->
|
<!-- FullCalendar -->
|
||||||
<div class="calendar-body p-6">
|
<div class="calendar-body p-6">
|
||||||
<FullCalendar ref="fullCalendar" :options="calendarOptions" />
|
<FullCalendar ref="fullCalendar" :key="calendarKey" :options="calendarOptions" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,6 +122,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, computed } from 'vue';
|
import { ref, onMounted, computed } from 'vue';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import FullCalendar from '@fullcalendar/vue3';
|
import FullCalendar from '@fullcalendar/vue3';
|
||||||
import dayGridPlugin from '@fullcalendar/daygrid';
|
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||||
import interactionPlugin from '@fullcalendar/interaction';
|
import interactionPlugin from '@fullcalendar/interaction';
|
||||||
@@ -131,11 +132,13 @@ import { serviceService } from '../../services/serviceService';
|
|||||||
import { authService } from '../../services/authService';
|
import { authService } from '../../services/authService';
|
||||||
import { AxiosResponse, AxiosError, ServiceCall } from '../../types/models';
|
import { AxiosResponse, AxiosError, ServiceCall } from '../../types/models';
|
||||||
import { getFederalHolidays, type Holiday } from '../../utils/holidays';
|
import { getFederalHolidays, type Holiday } from '../../utils/holidays';
|
||||||
|
import { notify } from '@kyvg/vue3-notification';
|
||||||
|
|
||||||
// Reactive data
|
// Reactive data
|
||||||
const user = ref(null)
|
const user = ref(null)
|
||||||
const selectedServiceForEdit = ref(null as Partial<ServiceCall> | null)
|
const selectedServiceForEdit = ref(null as Partial<ServiceCall> | null)
|
||||||
const fullCalendar = ref()
|
const fullCalendar = ref()
|
||||||
|
const calendarKey = ref(0)
|
||||||
const currentView = ref('dayGridMonth')
|
const currentView = ref('dayGridMonth')
|
||||||
const holidays = ref<Holiday[]>([])
|
const holidays = ref<Holiday[]>([])
|
||||||
const currentDate = ref(new Date())
|
const currentDate = ref(new Date())
|
||||||
@@ -163,9 +166,10 @@ const isHolidayDate = (dateStr: string): Holiday | undefined => {
|
|||||||
|
|
||||||
// Event handlers
|
// Event handlers
|
||||||
const handleEventClick = (clickInfo: EventClickArg): void => {
|
const handleEventClick = (clickInfo: EventClickArg): void => {
|
||||||
|
const start = clickInfo.event.start;
|
||||||
selectedServiceForEdit.value = {
|
selectedServiceForEdit.value = {
|
||||||
id: parseInt(clickInfo.event.id),
|
id: parseInt(clickInfo.event.id),
|
||||||
scheduled_date: clickInfo.event.startStr,
|
scheduled_date: start ? dayjs(start).format('YYYY-MM-DDTHH:mm:ss') : '',
|
||||||
customer_name: clickInfo.event.title.split(': ')[1] || 'Unknown Customer',
|
customer_name: clickInfo.event.title.split(': ')[1] || 'Unknown Customer',
|
||||||
customer_id: clickInfo.event.extendedProps.customer_id,
|
customer_id: clickInfo.event.extendedProps.customer_id,
|
||||||
type_service_call: clickInfo.event.extendedProps.type_service_call,
|
type_service_call: clickInfo.event.extendedProps.type_service_call,
|
||||||
@@ -182,12 +186,8 @@ const TYPE_CLASS: Record<number, string> = {
|
|||||||
4: 'event-other',
|
4: 'event-other',
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch events function for FullCalendar
|
// Fetch and render all events imperatively
|
||||||
const fetchCalendarEvents = async (
|
const fetchCalendarEvents = async () => {
|
||||||
fetchInfo: { startStr: string; endStr: string },
|
|
||||||
successCallback: (events: any[]) => void,
|
|
||||||
failureCallback: (error: Error) => void
|
|
||||||
) => {
|
|
||||||
try {
|
try {
|
||||||
const response = await serviceService.getAll();
|
const response = await serviceService.getAll();
|
||||||
const rawEvents: any[] = response.data?.events || [];
|
const rawEvents: any[] = response.data?.events || [];
|
||||||
@@ -206,14 +206,19 @@ const fetchCalendarEvents = async (
|
|||||||
start: holiday.date,
|
start: holiday.date,
|
||||||
allDay: true,
|
allDay: true,
|
||||||
display: 'background',
|
display: 'background',
|
||||||
classNames: ['holiday-event']
|
classNames: ['holiday-event'],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
successCallback([...serviceEvents, ...holidayEvents]);
|
const allEvents = [...serviceEvents, ...holidayEvents];
|
||||||
|
calendarOptions.value.events = allEvents as any;
|
||||||
|
|
||||||
|
const api = (fullCalendar.value as any)?.getApi();
|
||||||
|
if (api) {
|
||||||
|
api.removeAllEvents();
|
||||||
|
allEvents.forEach((e: any) => api.addEvent(e));
|
||||||
|
}
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
const error = err as AxiosError;
|
console.error("Failed to fetch calendar events:", err);
|
||||||
console.error("Failed to fetch calendar events:", error);
|
|
||||||
failureCallback(error as Error);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -280,7 +285,7 @@ const calendarOptions = ref({
|
|||||||
headerToolbar: false,
|
headerToolbar: false,
|
||||||
weekends: true,
|
weekends: true,
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
events: fetchCalendarEvents,
|
events: [],
|
||||||
eventClick: handleEventClick,
|
eventClick: handleEventClick,
|
||||||
dayCellClassNames: getDayCellClassNames,
|
dayCellClassNames: getDayCellClassNames,
|
||||||
eventDisplay: 'block',
|
eventDisplay: 'block',
|
||||||
@@ -297,24 +302,21 @@ const closeEditModal = () => {
|
|||||||
const handleSaveChanges = async (updatedService: ServiceCall) => {
|
const handleSaveChanges = async (updatedService: ServiceCall) => {
|
||||||
try {
|
try {
|
||||||
await serviceService.update(updatedService.id, updatedService);
|
await serviceService.update(updatedService.id, updatedService);
|
||||||
const calendarApi = (fullCalendar.value as any).getApi();
|
await fetchCalendarEvents();
|
||||||
if (calendarApi) {
|
calendarKey.value++;
|
||||||
calendarApi.refetchEvents();
|
|
||||||
}
|
|
||||||
closeEditModal();
|
closeEditModal();
|
||||||
|
notify({ title: 'Saved', text: 'Service call updated.', type: 'success' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to save changes:", error);
|
console.error("Failed to save changes:", error);
|
||||||
alert("An error occurred while saving. Please check the console.");
|
notify({ title: 'Error', text: 'Failed to save service call.', type: 'error' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDeleteService = async (serviceId: number) => {
|
const handleDeleteService = async (serviceId: number) => {
|
||||||
try {
|
try {
|
||||||
await serviceService.delete(serviceId);
|
await serviceService.delete(serviceId);
|
||||||
const calendarApi = (fullCalendar.value as any).getApi();
|
await fetchCalendarEvents();
|
||||||
if (calendarApi) {
|
calendarKey.value++;
|
||||||
calendarApi.refetchEvents();
|
|
||||||
}
|
|
||||||
closeEditModal();
|
closeEditModal();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error deleting event:", error);
|
console.error("Error deleting event:", error);
|
||||||
@@ -333,9 +335,10 @@ const userStatus = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
userStatus();
|
userStatus();
|
||||||
loadHolidays();
|
loadHolidays();
|
||||||
|
await fetchCalendarEvents();
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -163,19 +163,13 @@ const getServiceParts = (customerId: number) => {
|
|||||||
.finally(() => { isLoadingParts.value = false; });
|
.finally(() => { isLoadingParts.value = false; });
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveChanges = async () => {
|
const saveChanges = () => {
|
||||||
const date = editableService.value.date;
|
const date = editableService.value.date;
|
||||||
const time = editableService.value.time || 0;
|
const time = editableService.value.time ?? 0;
|
||||||
const combinedDateTime = dayjs(`${date} ${time}:00`).format('YYYY-MM-DDTHH:mm:ss');
|
const combinedDateTime = dayjs(`${date}T${String(time).padStart(2, '0')}:00:00`).format('YYYY-MM-DDTHH:mm:ss');
|
||||||
|
console.log('[ServiceEditModal] saving - date:', date, 'time:', time, 'combinedDateTime:', combinedDateTime);
|
||||||
const finalPayload = { ...props.service, ...editableService.value, scheduled_date: combinedDateTime };
|
const finalPayload = { ...props.service, ...editableService.value, scheduled_date: combinedDateTime };
|
||||||
|
|
||||||
try {
|
|
||||||
await serviceService.update(finalPayload.id!, finalPayload);
|
|
||||||
emit('save-changes', finalPayload as ServiceCall);
|
emit('save-changes', finalPayload as ServiceCall);
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to save changes:", error);
|
|
||||||
alert("An error occurred while saving. Please check the console.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const confirmDelete = () => {
|
const confirmDelete = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user