big update
This commit is contained in:
parent
73d235b7bc
commit
0380dd10d8
18 changed files with 951 additions and 490 deletions
|
|
@ -1,97 +1,135 @@
|
|||
<template>
|
||||
<Modal :visible="showModal" @update:visible="showModal = $event" :options="modalOptions" @confirm="handleClose">
|
||||
<template #title>Meeting Details</template>
|
||||
<template #title>
|
||||
<div class="modal-header">
|
||||
<i class="pi pi-calendar" style="color: var(--primary-color); font-size: 1.2rem; margin-right: 0.5rem;"></i>
|
||||
Meeting Details
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="meeting" class="meeting-details">
|
||||
<!-- Meeting ID -->
|
||||
<div class="detail-row">
|
||||
<v-icon class="mr-2">mdi-identifier</v-icon>
|
||||
<strong>Meeting ID:</strong>
|
||||
<span class="detail-value">{{ meeting.name }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Address -->
|
||||
<div class="detail-row">
|
||||
<v-icon class="mr-2">mdi-map-marker</v-icon>
|
||||
<strong>Address:</strong>
|
||||
<span class="detail-value">{{ meeting.address?.fullAddress || meeting.address }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Contact -->
|
||||
<div class="detail-row" v-if="meeting.contact">
|
||||
<v-icon class="mr-2">mdi-account</v-icon>
|
||||
<strong>Contact:</strong>
|
||||
<span class="detail-value">{{ meeting.contact }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Party Name (Customer) -->
|
||||
<div class="detail-row" v-if="meeting.partyName">
|
||||
<v-icon class="mr-2">mdi-account-group</v-icon>
|
||||
<strong>Customer:</strong>
|
||||
<span class="detail-value">{{ meeting.partyName }} ({{ meeting.partyType }})</span>
|
||||
</div>
|
||||
|
||||
<!-- Project Template -->
|
||||
<div class="detail-row" v-if="meeting.projectTemplate">
|
||||
<v-icon class="mr-2">mdi-folder-outline</v-icon>
|
||||
<strong>Template:</strong>
|
||||
<span class="detail-value">{{ meeting.projectTemplate }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Scheduled Time -->
|
||||
<div class="detail-row" v-if="meeting.startTime">
|
||||
<v-icon class="mr-2">mdi-calendar-clock</v-icon>
|
||||
<strong>Scheduled:</strong>
|
||||
<span class="detail-value">{{ formatDateTime(meeting.startTime) }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Duration -->
|
||||
<div class="detail-row" v-if="meeting.startTime && meeting.endTime">
|
||||
<v-icon class="mr-2">mdi-timer</v-icon>
|
||||
<strong>Duration:</strong>
|
||||
<span class="detail-value">{{ calculateDuration(meeting.startTime, meeting.endTime) }} minutes</span>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="detail-row">
|
||||
<v-icon class="mr-2">mdi-check-circle</v-icon>
|
||||
<strong>Status:</strong>
|
||||
<v-chip size="small" :color="getStatusColor(meeting.status)">
|
||||
<!-- Status Badge -->
|
||||
<div class="status-section">
|
||||
<div class="status-badge" :class="`status-${meeting.status?.toLowerCase()}`">
|
||||
<i class="pi pi-circle-fill"></i>
|
||||
{{ meeting.status }}
|
||||
</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Assigned Employee -->
|
||||
<div class="detail-row" v-if="meeting.assignedEmployee">
|
||||
<v-icon class="mr-2">mdi-account-tie</v-icon>
|
||||
<strong>Assigned To:</strong>
|
||||
<span class="detail-value">{{ meeting.assignedEmployee }}</span>
|
||||
<!-- Key Information Grid -->
|
||||
<div class="info-grid">
|
||||
<!-- Customer Name -->
|
||||
<div class="info-card" v-if="customerName">
|
||||
<div class="info-label">
|
||||
<i class="pi pi-user"></i>
|
||||
Customer
|
||||
</div>
|
||||
<div class="info-value">{{ customerName }}</div>
|
||||
<div class="info-meta" v-if="meeting.partyType">{{ meeting.partyType }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Project Template -->
|
||||
<div class="info-card" v-if="meeting.projectTemplate">
|
||||
<div class="info-label">
|
||||
<i class="pi pi-folder"></i>
|
||||
Project Type
|
||||
</div>
|
||||
<div class="info-value">{{ meeting.projectTemplate }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Scheduled Time -->
|
||||
<div class="info-card" v-if="meeting.startTime">
|
||||
<div class="info-label">
|
||||
<i class="pi pi-clock"></i>
|
||||
Scheduled
|
||||
</div>
|
||||
<div class="info-value">{{ formatDateTime(meeting.startTime) }}</div>
|
||||
<div class="info-meta" v-if="meeting.endTime">Duration: {{ calculateDuration(meeting.startTime, meeting.endTime) }} min</div>
|
||||
</div>
|
||||
|
||||
<!-- Assigned Employee -->
|
||||
<div class="info-card" v-if="meeting.assignedEmployee">
|
||||
<div class="info-label">
|
||||
<i class="pi pi-user-edit"></i>
|
||||
Assigned To
|
||||
</div>
|
||||
<div class="info-value">{{ meeting.assignedEmployee }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Completed By -->
|
||||
<div class="detail-row" v-if="meeting.completedBy">
|
||||
<v-icon class="mr-2">mdi-account-check</v-icon>
|
||||
<strong>Completed By:</strong>
|
||||
<span class="detail-value">{{ meeting.completedBy }}</span>
|
||||
<!-- Address Section -->
|
||||
<div class="section-divider">
|
||||
<i class="pi pi-map-marker"></i>
|
||||
<span>Location</span>
|
||||
</div>
|
||||
<div class="address-section">
|
||||
<div class="address-text">
|
||||
<strong>{{ addressText }}</strong>
|
||||
<div class="meeting-id">ID: {{ meeting.name }}</div>
|
||||
</div>
|
||||
<div v-if="hasCoordinates" class="map-container">
|
||||
<iframe
|
||||
:src="mapUrl"
|
||||
width="100%"
|
||||
height="200"
|
||||
frameborder="0"
|
||||
style="border: 1px solid var(--surface-border); border-radius: 6px;"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Company -->
|
||||
<div class="detail-row" v-if="meeting.company">
|
||||
<v-icon class="mr-2">mdi-domain</v-icon>
|
||||
<strong>Company:</strong>
|
||||
<span class="detail-value">{{ meeting.company }}</span>
|
||||
<!-- Contact Information -->
|
||||
<div class="section-divider" v-if="contactInfo">
|
||||
<i class="pi pi-phone"></i>
|
||||
<span>Contact Information</span>
|
||||
</div>
|
||||
<div class="contact-section" v-if="contactInfo">
|
||||
<div class="contact-item">
|
||||
<i class="pi pi-user"></i>
|
||||
<span class="contact-label">Name:</span>
|
||||
<span class="contact-value">{{ contactInfo.fullName }}</span>
|
||||
</div>
|
||||
<div class="contact-item" v-if="contactInfo.role">
|
||||
<i class="pi pi-briefcase"></i>
|
||||
<span class="contact-label">Role:</span>
|
||||
<span class="contact-value">{{ contactInfo.role }}</span>
|
||||
</div>
|
||||
<div class="contact-item" v-if="contactInfo.phone">
|
||||
<i class="pi pi-phone"></i>
|
||||
<span class="contact-label">Phone:</span>
|
||||
<a :href="`tel:${contactInfo.phone}`" class="contact-value contact-link">{{ contactInfo.phone }}</a>
|
||||
</div>
|
||||
<div class="contact-item" v-if="contactInfo.email">
|
||||
<i class="pi pi-envelope"></i>
|
||||
<span class="contact-label">Email:</span>
|
||||
<a :href="`mailto:${contactInfo.email}`" class="contact-value contact-link">{{ contactInfo.email }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notes -->
|
||||
<div class="detail-row" v-if="meeting.notes">
|
||||
<v-icon class="mr-2">mdi-note-text</v-icon>
|
||||
<strong>Notes:</strong>
|
||||
<span class="detail-value">{{ meeting.notes }}</span>
|
||||
<div v-if="meeting.notes" class="notes-section">
|
||||
<div class="section-divider">
|
||||
<i class="pi pi-file-edit"></i>
|
||||
<span>Notes</span>
|
||||
</div>
|
||||
<div class="notes-content">{{ meeting.notes }}</div>
|
||||
</div>
|
||||
|
||||
<!-- Additional Info -->
|
||||
<div class="additional-info" v-if="meeting.company || meeting.completedBy">
|
||||
<div class="info-item" v-if="meeting.company">
|
||||
<i class="pi pi-building"></i>
|
||||
<span>{{ meeting.company }}</span>
|
||||
</div>
|
||||
<div class="info-item" v-if="meeting.completedBy">
|
||||
<i class="pi pi-check-circle"></i>
|
||||
<span>Completed by: {{ meeting.completedBy }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="action-buttons">
|
||||
<v-btn
|
||||
v-if="meeting.status !== 'Completed'"
|
||||
v-if="meeting.status !== 'Completed' && meeting.status !== 'Unscheduled'"
|
||||
@click="handleMarkComplete"
|
||||
color="success"
|
||||
variant="elevated"
|
||||
|
|
@ -154,12 +192,75 @@ const showModal = computed({
|
|||
|
||||
// Modal options
|
||||
const modalOptions = computed(() => ({
|
||||
maxWidth: "700px",
|
||||
maxWidth: "800px",
|
||||
showCancelButton: false,
|
||||
confirmButtonText: "Close",
|
||||
confirmButtonColor: "primary",
|
||||
}));
|
||||
|
||||
// Computed properties for data extraction
|
||||
const customerName = computed(() => {
|
||||
if (props.meeting?.address?.customerName) {
|
||||
return props.meeting.address.customerName;
|
||||
}
|
||||
if (props.meeting?.partyName) {
|
||||
return props.meeting.partyName;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const addressText = computed(() => {
|
||||
return props.meeting?.address?.fullAddress || props.meeting?.address || "";
|
||||
});
|
||||
|
||||
const hasCoordinates = computed(() => {
|
||||
const lat = props.meeting?.address?.customLatitude || props.meeting?.address?.latitude;
|
||||
const lon = props.meeting?.address?.customLongitude || props.meeting?.address?.longitude;
|
||||
return lat && lon && parseFloat(lat) !== 0 && parseFloat(lon) !== 0;
|
||||
});
|
||||
|
||||
const mapUrl = computed(() => {
|
||||
if (!hasCoordinates.value) return "";
|
||||
const lat = parseFloat(props.meeting?.address?.customLatitude || props.meeting?.address?.latitude);
|
||||
const lon = parseFloat(props.meeting?.address?.customLongitude || props.meeting?.address?.longitude);
|
||||
const zoom = 15;
|
||||
// Using OpenStreetMap embed with marker
|
||||
return `https://www.openstreetmap.org/export/embed.html?bbox=${lon - 0.01},${lat - 0.01},${lon + 0.01},${lat + 0.01}&layer=mapnik&marker=${lat},${lon}`;
|
||||
});
|
||||
|
||||
const contactInfo = computed(() => {
|
||||
console.log('=== CONTACT DEBUG ===');
|
||||
console.log('Full meeting object:', props.meeting);
|
||||
console.log('Meeting contact value:', props.meeting?.contact);
|
||||
console.log('Contact type:', typeof props.meeting?.contact);
|
||||
|
||||
const contact = props.meeting?.contact;
|
||||
if (!contact) {
|
||||
console.log('No contact found - returning null');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Handle both string and object contact
|
||||
if (typeof contact === 'string') {
|
||||
console.log('Contact is a string:', contact);
|
||||
return { fullName: contact };
|
||||
}
|
||||
|
||||
// Log the contact object to see what properties are available
|
||||
console.log('Contact object keys:', Object.keys(contact));
|
||||
console.log('Contact object:', contact);
|
||||
|
||||
const contactData = {
|
||||
fullName: contact.name || contact.fullName || contact.contactName || `${contact.firstName || ''} ${contact.lastName || ''}`.trim() || '',
|
||||
phone: contact.phone || contact.mobileNo || contact.mobile || contact.phoneNos?.[0]?.phone || '',
|
||||
email: contact.emailId || contact.email || contact.emailAddress || contact.emailIds?.[0]?.emailId || '',
|
||||
role: contact.role || contact.designation || '',
|
||||
};
|
||||
|
||||
console.log('Extracted contact data:', contactData);
|
||||
return contactData;
|
||||
});
|
||||
|
||||
// Methods
|
||||
const handleClose = () => {
|
||||
emit("close");
|
||||
|
|
@ -204,32 +305,20 @@ const handleCreateEstimate = () => {
|
|||
const addressText = props.meeting.address?.fullAddress || props.meeting.address || "";
|
||||
const template = props.meeting.projectTemplate || "";
|
||||
const fromMeeting = props.meeting.name || "";
|
||||
const contactName = props.meeting.contact?.name || "";
|
||||
|
||||
router.push({
|
||||
path: "/estimate",
|
||||
query: {
|
||||
new: "true",
|
||||
address: addressText,
|
||||
"from-meeting": fromMeeting,
|
||||
template: template,
|
||||
from: fromMeeting,
|
||||
contact: contactName,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const formatDateTime = (dateTimeStr) => {
|
||||
if (!dateTimeStr) return "";
|
||||
const date = new Date(dateTimeStr);
|
||||
return date.toLocaleString("en-US", {
|
||||
weekday: "short",
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: true,
|
||||
});
|
||||
};
|
||||
|
||||
const calculateDuration = (startTime, endTime) => {
|
||||
if (!startTime || !endTime) return 0;
|
||||
const start = new Date(startTime);
|
||||
|
|
@ -238,28 +327,9 @@ const calculateDuration = (startTime, endTime) => {
|
|||
return Math.round(diffMs / (1000 * 60)); // Convert to minutes
|
||||
};
|
||||
|
||||
const getStatusColor = (status) => {
|
||||
const statusColors = {
|
||||
Unscheduled: "warning",
|
||||
Scheduled: "info",
|
||||
Completed: "success",
|
||||
Cancelled: "error",
|
||||
};
|
||||
return statusColors[status] || "default";
|
||||
};
|
||||
|
||||
const formatTimeDisplay = (time) => {
|
||||
if (!time) return "";
|
||||
const [hours, minutes] = time.split(":").map(Number);
|
||||
const displayHour = hours > 12 ? hours - 12 : hours === 0 ? 12 : hours;
|
||||
const ampm = hours >= 12 ? "PM" : "AM";
|
||||
return `${displayHour}:${minutes.toString().padStart(2, "0")} ${ampm}`;
|
||||
};
|
||||
|
||||
const formatDate = (dateStr) => {
|
||||
if (!dateStr) return "";
|
||||
const date = new Date(dateStr);
|
||||
return date.toLocaleDateString("en-US", {
|
||||
const formatDateTime = (dateString) => {
|
||||
if (!dateString) return "";
|
||||
return new Date(dateString).toLocaleString("en-US", {
|
||||
weekday: "long",
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue