+ :style="getJobStyle(job, day.date)"
+ :draggable="job.status === 'Scheduled'"
+ @click.stop="showEventDetails({ event: job })"
+ @dragstart="job.status === 'Scheduled' ? handleDragStart(job, $event) : null"
+ @dragend="handleDragEnd"
+ @mousedown="(job.status === 'Scheduled' || job.status === 'Started') ? startResize($event, job, day.date) : null"
+ @drop.stop.prevent="handleScheduledDrop(job, $event, foreman.name, day.date)"
+ @dragover.stop.prevent="handleScheduledDragOver(job, $event, foreman.name, day.date)"
+ >
mdi-arrow-left
-
{{ job.projectTemplate || job.serviceType }}
-
{{ stripAddress(job.address || job.jobAddress) }}
+
{{ job.projectTemplate }}
+
{{ job.serviceAddress.fullAddress }}
mdi-arrow-right
@@ -225,7 +221,7 @@
@@ -241,10 +237,10 @@
@@ -255,46 +251,47 @@
@dragleave="handleUnscheduledDragLeave"
@drop="handleUnscheduledDrop"
>
-
-
- {{ service.projectTemplate || service.serviceType }}
-
- mdi-map-marker
- {{ stripAddress(service.address || service.jobAddress) }}
-
-
- mdi-account
- {{ service.customer }}
-
-
-
- {{ service.notes }}
-
-
-
- mdi-calendar-plus
- Schedule
-
-
-
+
+
+
+ {{ service.project?.projectName || service.projectTemplate || service.name }}
+
+
+ mdi-map-marker
+ {{ service.serviceAddress?.fullAddress || service.serviceAddress?.addressTitle || service.serviceAddress?.name || service.project?.jobAddress }}
+
+
+ mdi-account
+ {{ service.customer?.customerName || service.customer?.name || service.project?.customer || service.customer || 'N/A' }}
+
+
+ {{ service.project?.notes || service.notes }}
+
+
+ mdi-calendar-plus
+ Schedule
+
+
+
@@ -309,6 +306,9 @@
v-model="eventDialog"
:job="selectedEvent"
:foremen="foremen"
+ :project="selectedEvent?.project"
+ :customer="selectedEvent?.customer"
+ :service-address="selectedEvent?.serviceAddress"
@close="eventDialog = false"
/>
@@ -330,7 +330,8 @@ const notifications = useNotificationStore();
const companyStore = useCompanyStore();
// Reactive data
-const services = ref([]);
+const scheduledServices = ref([]);
+const unscheduledServices = ref([]);
const weekStartDate = ref(getWeekStart(new Date()));
const eventDialog = ref(false);
const selectedEvent = ref(null);
@@ -479,7 +480,7 @@ function hasHolidayInRange(startDate, endDate) {
// Helper function to check if job spans to next week
function jobSpansToNextWeek(job) {
- const endDate = job.scheduledEndDate || job.scheduledDate;
+ const endDate = job.expectedEndDate || job.expectedStartDate;
const end = parseLocalDate(endDate);
const weekEnd = parseLocalDate(addDays(weekStartDate.value, 6));
return end > weekEnd;
@@ -487,18 +488,12 @@ function jobSpansToNextWeek(job) {
// Helper function to check if job starts before current week
function jobStartsBeforeWeek(job) {
- const startDate = job.expectedStartDate || job.scheduledDate;
+ const startDate = job.expectedStartDate;
const start = parseLocalDate(startDate);
const weekStart = parseLocalDate(weekStartDate.value);
return start < weekStart;
}
-// Helper function to strip address after "-#-"
-function stripAddress(address) {
- if (!address) return '';
- const index = address.indexOf('-#-');
- return index > -1 ? address.substring(0, index).trim() : address;
-}
// Helper function to get crew name from foreman ID
function getCrewName(foremanId) {
@@ -524,8 +519,8 @@ function getHolidaysInRange(startDate, endDate) {
// Helper function to calculate job segments (parts between holidays/Sundays)
function getJobSegments(job) {
- const startDate = job.scheduledDate;
- const endDate = job.scheduledEndDate || job.scheduledDate;
+ const startDate = job.expectedStartDate;
+ const endDate = job.expectedEndDate || job.expectedStartDate;
const weekEndDate = addDays(weekStartDate.value, 6); // Saturday
const segments = [];
@@ -598,16 +593,9 @@ const visibleForemen = computed(() => {
});
});
-const scheduledServices = computed(() =>
- services.value.filter((service) => service.isScheduled && service.foreman),
-);
-const unscheduledServices = computed(() =>
- services.value.filter((service) => !service.isScheduled || !service.foreman),
-);
// Methods
-const getUnscheduledCount = () => unscheduledServices.value.length;
const getPriorityClass = (priority) => {
switch (priority) {
@@ -655,8 +643,8 @@ const getJobsForCell = (foremanId, date) => {
return scheduledServices.value.filter((job) => {
if (job.foreman !== foremanId) return false;
- const jobStart = job.scheduledDate;
- const jobEnd = job.scheduledEndDate || job.scheduledDate;
+ const jobStart = job.expectedStartDate;
+ const jobEnd = job.expectedEndDate || job.expectedStartDate;
// Check if this date falls within the job's date range
// AND that it's a valid segment start date
@@ -672,8 +660,8 @@ const getJobsWithConnector = (foremanId, date) => {
return scheduledServices.value.filter((job) => {
if (job.foreman !== foremanId) return false;
- const jobStart = job.scheduledDate;
- const jobEnd = job.scheduledEndDate || job.scheduledDate;
+ const jobStart = job.expectedStartDate;
+ const jobEnd = job.expectedEndDate || job.expectedStartDate;
// Check if this holiday date falls within the job's range
return date > jobStart && date < jobEnd;
@@ -682,8 +670,8 @@ const getJobsWithConnector = (foremanId, date) => {
// Get job style for width spanning multiple days
const getJobStyle = (job, currentDate) => {
- const jobStart = job.scheduledDate;
- const jobEnd = job.scheduledEndDate || job.scheduledDate;
+ const jobStart = job.expectedStartDate;
+ const jobEnd = job.expectedEndDate || job.expectedStartDate;
const segments = getJobSegments(job);
// Find which segment (if any) should be rendered at currentDate
@@ -842,7 +830,7 @@ const scheduleService = (service) => {
const handleDragStart = (service, event) => {
draggedService.value = service;
event.dataTransfer.effectAllowed = "move";
- event.dataTransfer.setData("text/plain", service.id);
+ event.dataTransfer.setData("text/plain", service.name);
// Get the dimensions of the dragged element
const dragElement = event.target;
@@ -860,7 +848,7 @@ const handleDragStart = (service, event) => {
// Add visual feedback
event.target.style.opacity = '0.5';
- console.log("Drag started for service:", service.serviceType);
+ console.log("Drag started for service:", service.projectTemplate);
};
const handleDragEnd = (event) => {
@@ -962,43 +950,61 @@ const handleDrop = async (event, foremanId, date) => {
return;
}
- // Update the service with scheduling information
- const serviceIndex = services.value.findIndex(s => s.id === draggedService.value.id);
- if (serviceIndex !== -1) {
- const wasScheduled = services.value[serviceIndex].isScheduled;
+ // Check if this is scheduling an unscheduled job or moving a scheduled job
+ const unscheduledIndex = unscheduledServices.value.findIndex(s => s.name === draggedService.value.name);
+ const scheduledIndex = scheduledServices.value.findIndex(s => s.name === draggedService.value.name);
+
+ if (unscheduledIndex !== -1) {
+ // Scheduling an unscheduled job
+ const action = 'Scheduled';
+ console.log(`${action} ${draggedService.value.projectTemplate} for ${foreman.employeeName} on ${date} to ${endDate}`);
-
- const action = wasScheduled ? 'Moved' : 'Scheduled';
- console.log(`${action} ${draggedService.value.serviceType} for ${foreman.employeeName} on ${date} to ${endDate}`);
-
- // Call API to persist changes (placeholder for now)
+ // Call API to persist changes
try {
- await Api.updateJobScheduledDates(
- draggedService.value.id,
+ await Api.updateServiceAppointmentScheduledDates(
+ draggedService.value.name,
date,
endDate,
foreman.name
- )
- services.value[serviceIndex] = {
- ...services.value[serviceIndex],
- isScheduled: true,
- scheduledDate: date,
- scheduledEndDate: endDate,
- foreman: foreman.name
+ );
+ // Remove from unscheduled and add to scheduled
+ const scheduledService = {
+ ...unscheduledServices.value[unscheduledIndex],
+ expectedStartDate: date,
+ expectedEndDate: endDate,
+ foreman: foreman.name,
+ status: 'Scheduled'
};
+ unscheduledServices.value.splice(unscheduledIndex, 1);
+ scheduledServices.value.push(scheduledService);
notifications.addSuccess("Job scheduled successfully!");
- // Future implementation:
- // await Api.updateJobSchedule({
- // id: draggedService.value.id,
- // expectedStartDate: date,
- // expectedEndDate: endDate,
- // isScheduled: true,
- // customForeman: foreman.name
- // });
} catch (error) {
console.error("Error scheduling job:", error);
notifications.addError("Failed to schedule job");
}
+ } else if (scheduledIndex !== -1 && draggedService.value.status === 'Scheduled') {
+ // Moving a scheduled job to a new date/foreman
+ console.log(`Moving ${draggedService.value.projectTemplate} to ${foreman.employeeName} on ${date}`);
+
+ // Call API to persist changes
+ try {
+ await Api.updateServiceAppointmentScheduledDates(
+ draggedService.value.name,
+ date,
+ draggedService.value.expectedEndDate, // Keep the same end date
+ foreman.name
+ );
+ // Update the scheduled job
+ scheduledServices.value[scheduledIndex] = {
+ ...scheduledServices.value[scheduledIndex],
+ expectedStartDate: date,
+ foreman: foreman.name
+ };
+ notifications.addSuccess("Job moved successfully!");
+ } catch (error) {
+ console.error("Error moving job:", error);
+ notifications.addError("Failed to move job");
+ }
}
// Reset drag state
@@ -1009,7 +1015,8 @@ const handleDrop = async (event, foremanId, date) => {
// Handle dropping scheduled items back to unscheduled
const handleUnscheduledDragOver = (event) => {
- if (draggedService.value?.isScheduled) {
+ // Only allow dropping if the dragged job is scheduled
+ if (draggedService.value && draggedService.value.status === 'Scheduled') {
event.preventDefault();
event.dataTransfer.dropEffect = "move";
}
@@ -1021,79 +1028,127 @@ const handleUnscheduledDragLeave = (event) => {
const handleUnscheduledDrop = async (event) => {
event.preventDefault();
-
- if (!draggedService.value || !draggedService.value.isScheduled) return;
-
+
+ if (!draggedService.value) return;
+ // Only allow unscheduling if status is 'Scheduled'
+ if (draggedService.value.status !== 'Scheduled') {
+ notifications.addError('Only jobs with status "Scheduled" can be unscheduled.');
+ isDragOver.value = false;
+ dragOverCell.value = null;
+ draggedService.value = null;
+ return;
+ }
// Update the service to unscheduled status
- const serviceIndex = services.value.findIndex(s => s.id === draggedService.value.id);
+ const serviceIndex = scheduledServices.value.findIndex(s => s.name === draggedService.value.name);
if (serviceIndex !== -1) {
- services.value[serviceIndex] = {
- ...services.value[serviceIndex],
- isScheduled: false,
- scheduledDate: null,
- scheduledEndDate: null,
+ // Remove from scheduled and add to unscheduled
+ const unscheduledService = {
+ ...scheduledServices.value[serviceIndex],
+ expectedStartDate: null,
+ expectedEndDate: null,
foreman: null
};
-
- console.log(`Unscheduled ${draggedService.value.serviceType}`);
+ scheduledServices.value.splice(serviceIndex, 1);
+ unscheduledServices.value.push(unscheduledService);
+
+ console.log(`Unscheduled ${draggedService.value.projectTemplate}`);
- // Call API to persist changes (placeholder for now)
+ // Call API to persist changes
try {
- await Api.updateJobScheduledDates(
- draggedService.value.id,
+ await Api.updateServiceAppointmentScheduledDates(
+ draggedService.value.name,
null,
null,
null
)
notifications.addSuccess("Job unscheduled successfully!");
- // Future implementation:
- // await Api.updateJobSchedule({
- // id: draggedService.value.id,
- // expectedStartDate: null,
- // expectedEndDate: null,
- // isScheduled: false,
- // customForeman: null
- // });
} catch (error) {
console.error("Error unscheduling job:", error);
notifications.addError("Failed to unschedule job");
}
}
-
+
// Reset drag state
isDragOver.value = false;
dragOverCell.value = null;
draggedService.value = null;
};
+// Allow moving scheduled jobs between days/crews if status is 'Scheduled'
+const handleScheduledDragOver = (job, event, foremanId, date) => {
+ if (!draggedService.value) return;
+ // Only allow if dragging a scheduled job and target is a valid cell
+ if (draggedService.value.status === 'Scheduled' && job.status === 'Scheduled') {
+ event.dataTransfer.dropEffect = 'move';
+ }
+};
+
+const handleScheduledDrop = async (job, event, foremanId, date) => {
+ if (!draggedService.value) return;
+ // Only allow if dragging a scheduled job and target is a valid cell
+ if (draggedService.value.status !== 'Scheduled') return;
+ // Prevent dropping on same cell
+ if (draggedService.value.name === job.name && draggedService.value.foreman === foremanId && draggedService.value.expectedStartDate === date) return;
+
+ // Prevent dropping on Sunday or holidays
+ if (isSunday(date) || isHoliday(date)) return;
+
+ // Update job's foreman and date
+ const serviceIndex = scheduledServices.value.findIndex(s => s.name === draggedService.value.name);
+ if (serviceIndex !== -1) {
+ try {
+ await Api.updateServiceAppointmentScheduledDates(
+ draggedService.value.name,
+ date,
+ draggedService.value.expectedEndDate,
+ foremanId
+ );
+ scheduledServices.value[serviceIndex] = {
+ ...scheduledServices.value[serviceIndex],
+ expectedStartDate: date,
+ foreman: foremanId
+ };
+ notifications.addSuccess('Job moved successfully!');
+ } catch (error) {
+ notifications.addError('Failed to move job');
+ }
+ }
+ isDragOver.value = false;
+ dragOverCell.value = null;
+ draggedService.value = null;
+};
+
// Resize functionality
const startResize = (event, job, date) => {
// Only allow resize from the right edge
+ // Only allow if status is 'Scheduled' or 'Started'
+ if (!(job.status === 'Scheduled' || job.status === 'Started')) return;
+
const target = event.target;
const rect = target.getBoundingClientRect();
const clickX = event.clientX;
-
+
// Check if click is on the resize handle or near right edge
const isNearRightEdge = clickX > rect.right - 10;
const isResizeHandle = target.classList.contains('resize-handle');
-
+
if (!isNearRightEdge && !isResizeHandle) return;
-
+
event.stopPropagation();
event.preventDefault();
-
+
// Set flag immediately to prevent modal from opening
justFinishedResize.value = true;
-
+
resizingJob.value = job;
resizeStartX.value = event.clientX;
resizeStartDate.value = date;
- originalEndDate.value = job.scheduledEndDate || job.scheduledDate;
-
+ originalEndDate.value = job.expectedEndDate || job.expectedStartDate;
+
// Add global mouse move and mouse up listeners
document.addEventListener('mousemove', handleResize);
document.addEventListener('mouseup', stopResize);
-
+
// Add visual feedback
document.body.style.cursor = 'ew-resize';
};
@@ -1163,11 +1218,11 @@ const handleResize = (event) => {
// Show popup if extending over Sunday
showExtendToNextWeekPopup.value = extendsOverSunday;
- const serviceIndex = services.value.findIndex(s => s.id === resizingJob.value.id);
+ const serviceIndex = scheduledServices.value.findIndex(s => s.name === resizingJob.value.name);
if (serviceIndex !== -1) {
- services.value[serviceIndex] = {
- ...services.value[serviceIndex],
- scheduledEndDate: newEndDate
+ scheduledServices.value[serviceIndex] = {
+ ...scheduledServices.value[serviceIndex],
+ expectedEndDate: newEndDate
};
}
};
@@ -1181,12 +1236,12 @@ const stopResize = async () => {
const job = resizingJob.value;
// Find the updated job in services array (because resizingJob.value is a stale reference)
- const serviceIndex = services.value.findIndex(s => s.id === job.id);
+ const serviceIndex = scheduledServices.value.findIndex(s => s.name === job.name);
if (serviceIndex === -1) return;
- const updatedJob = services.value[serviceIndex];
- const newEndDate = updatedJob.scheduledEndDate || updatedJob.scheduledDate;
- console.log(`Proposed new end date for job ${job.serviceType}: ${newEndDate}`);
+ const updatedJob = scheduledServices.value[serviceIndex];
+ const newEndDate = updatedJob.expectedEndDate || updatedJob.expectedStartDate;
+ console.log(`Proposed new end date for job ${job.projectTemplate}: ${newEndDate}`);
// Only update if end date changed
if (newEndDate !== originalEndDate.value) {
@@ -1197,13 +1252,13 @@ const stopResize = async () => {
notifications.addInfo(`Job extended to next Monday (${formatDate(newEndDate)})`);
}
- console.log(`Extended job ${job.serviceType} from ${originalEndDate.value} to ${newEndDate}`);
+ console.log(`Extended job ${job.projectTemplate} from ${originalEndDate.value} to ${newEndDate}`);
// Call API to persist changes
try {
- await Api.updateJobScheduledDates(
- job.id,
- job.scheduledDate,
+ await Api.updateServiceAppointmentScheduledDates(
+ job.name,
+ job.expectedStartDate,
newEndDate,
job.foreman
);
@@ -1212,11 +1267,11 @@ const stopResize = async () => {
console.error("Error updating job end date:", error);
notifications.addError("Failed to update job end date");
// Revert on error
- const serviceIndex = services.value.findIndex(s => s.id === job.id);
+ const serviceIndex = scheduledServices.value.findIndex(s => s.name === job.name);
if (serviceIndex !== -1) {
- services.value[serviceIndex] = {
- ...services.value[serviceIndex],
- scheduledEndDate: originalEndDate.value
+ scheduledServices.value[serviceIndex] = {
+ ...scheduledServices.value[serviceIndex],
+ expectedEndDate: originalEndDate.value
};
}
}
@@ -1246,69 +1301,26 @@ const fetchServiceAppointments = async (currentDate) => {
const endDate = addDays(startDate, 6);
// const data = await Api.getJobsForCalendar(startDate, endDate, companyStore.currentCompany, selectedProjectTemplates.value);
- const data = await Api.getServiceAppointments(
+ scheduledServices.value = await Api.getServiceAppointments(
[companyStore.currentCompany],
{
"expectedStartDate": ["<=", endDate],
- "expectedEndDate": [">=", startDate]
+ "expectedEndDate": [">=", startDate],
+ "status": ["not in", ["Canceled"]]
}
);
- // Transform the API response into the format the component expects
- const transformedServices = [];
+ unscheduledServices.value = await Api.getServiceAppointments(
+ [companyStore.currentCompany],
+ {
+ "status": "Open"
+ }
+ );
- // Process scheduled projects
- if (data.projects && Array.isArray(data.projects)) {
- data.projects.forEach(project => {
- transformedServices.push({
- ...project, // Include all fields from API
- id: project.name,
- title: project.projectName || project.jobAddress || 'Unnamed Project',
- serviceType: project.projectName || project.jobAddress || 'Install Project',
- customer: project.customer || 'Unknown Customer',
- address: project.jobAddress || project.customInstallationAddress || '',
- scheduledDate: project.expectedStartDate || startDate,
- scheduledEndDate: project.expectedEndDate || project.expectedStartDate || startDate,
- foreman: project.customForeman,
- priority: (project.priority || 'Medium').toLowerCase(),
- estimatedCost: project.totalSalesAmount || project.estimatedCosting || 0,
- notes: project.notes || '',
- isScheduled: true,
- projectTemplate: project.projectTemplate,
- calendarColor: project.calendarColor,
- jobAddress: project.jobAddress
- });
- });
- }
-
- // Process unscheduled projects
- if (data.unscheduledProjects && Array.isArray(data.unscheduledProjects)) {
- data.unscheduledProjects.forEach(project => {
- transformedServices.push({
- ...project, // Include all fields from API
- id: project.name,
- title: project.projectName || project.jobAddress || 'Unnamed Project',
- serviceType: project.projectName || project.jobAddress || 'Install Project',
- customer: project.customer || 'Unknown Customer',
- address: project.jobAddress || project.customInstallationAddress || '',
- scheduledDate: null,
- scheduledEndDate: null,
- foreman: null,
- priority: (project.priority || 'Medium').toLowerCase(),
- estimatedCost: project.totalSalesAmount || project.estimatedCosting || 0,
- notes: project.notes || '',
- isScheduled: false,
- projectTemplate: project.projectTemplate,
- calendarColor: project.calendarColor,
- jobAddress: project.jobAddress
- });
- });
- }
-
- services.value = transformedServices;
- console.log("Loaded install projects:", transformedServices);
+ console.log("Loaded service appointments:", scheduledServices.value);
+ console.log("Loaded unscheduled service appointments:", unscheduledServices.value);
} catch (error) {
- console.error("Error loading install projects:", error);
- notifications.addError("Failed to load install projects");
+ console.error("Error loading service appointments:", error);
+ notifications.addError("Failed to load service appointments");
}
};
diff --git a/frontend/src/components/clientSubPages/AddressInformationForm.vue b/frontend/src/components/clientSubPages/AddressInformationForm.vue
index 622bfb9..632979d 100644
--- a/frontend/src/components/clientSubPages/AddressInformationForm.vue
+++ b/frontend/src/components/clientSubPages/AddressInformationForm.vue
@@ -59,6 +59,15 @@
style="margin-top: 0"
/>
+
+