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:
2026-06-18 14:39:16 -04:00
parent afdb9eb4e0
commit 3134ef0264
2 changed files with 32 additions and 35 deletions
+27 -24
View File
@@ -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>
+5 -11
View File
@@ -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 };
emit('save-changes', finalPayload as ServiceCall);
try {
await serviceService.update(finalPayload.id!, finalPayload);
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 = () => {