386 lines
9.3 KiB
Vue
386 lines
9.3 KiB
Vue
<template>
|
|
<div v-if="addresses.length > 1" class="address-selector">
|
|
<div class="selector-header">
|
|
<h4>Select Address</h4>
|
|
<Button
|
|
@click="showAddAddressModal = true"
|
|
icon="pi pi-plus"
|
|
label="Add An Address"
|
|
size="small"
|
|
severity="secondary"
|
|
/>
|
|
</div>
|
|
|
|
<Select
|
|
v-model="selectedAddressIndex"
|
|
:options="addressOptions"
|
|
optionLabel="label"
|
|
optionValue="value"
|
|
placeholder="Select an address"
|
|
class="w-full address-dropdown"
|
|
@change="handleAddressChange"
|
|
>
|
|
<template #value="slotProps">
|
|
<div v-if="slotProps.value !== null && slotProps.value !== undefined" class="dropdown-value">
|
|
<span class="address-title">{{ addresses[slotProps.value]?.fullAddress || 'Unnamed Address' }}</span>
|
|
<div class="address-badges">
|
|
<Badge
|
|
v-if="addresses[slotProps.value]?.isPrimaryAddress && !addresses[slotProps.value]?.isServiceAddress"
|
|
value="Billing Only"
|
|
severity="info"
|
|
/>
|
|
<Badge
|
|
v-if="addresses[slotProps.value]?.isPrimaryAddress && addresses[slotProps.value]?.isServiceAddress"
|
|
value="Billing & Service"
|
|
severity="success"
|
|
/>
|
|
<Badge
|
|
v-if="!addresses[slotProps.value]?.isPrimaryAddress && addresses[slotProps.value]?.isServiceAddress"
|
|
value="Service"
|
|
severity="secondary"
|
|
/>
|
|
<Badge
|
|
v-if="addresses[slotProps.value]?.isServiceAddress"
|
|
:value="`${addresses[slotProps.value]?.projects?.length || 0} Projects`"
|
|
severity="contrast"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template #option="slotProps">
|
|
<div class="dropdown-option">
|
|
<span class="option-title">{{ slotProps.option.addressTitle || 'Unnamed Address' }}</span>
|
|
<div class="option-badges">
|
|
<Badge
|
|
v-if="slotProps.option.isPrimaryAddress && !slotProps.option.isServiceAddress"
|
|
value="Billing Only"
|
|
severity="info"
|
|
size="small"
|
|
/>
|
|
<Badge
|
|
v-if="slotProps.option.isPrimaryAddress && slotProps.option.isServiceAddress"
|
|
value="Billing & Service"
|
|
severity="success"
|
|
size="small"
|
|
/>
|
|
<Badge
|
|
v-if="!slotProps.option.isPrimaryAddress && slotProps.option.isServiceAddress"
|
|
value="Service"
|
|
severity="secondary"
|
|
size="small"
|
|
/>
|
|
<Badge
|
|
v-if="slotProps.option.isServiceAddress"
|
|
:value="`${slotProps.option.projectCount} Projects`"
|
|
severity="contrast"
|
|
size="small"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</Select>
|
|
|
|
<!-- Selected Address Info -->
|
|
<div v-if="selectedAddress" class="selected-address-info">
|
|
<div class="address-status">
|
|
<Badge
|
|
v-if="selectedAddress.isPrimaryAddress && !selectedAddress.isServiceAddress"
|
|
value="Billing Only Address"
|
|
severity="info"
|
|
/>
|
|
<Badge
|
|
v-if="selectedAddress.isPrimaryAddress && selectedAddress.isServiceAddress"
|
|
value="Billing & Service Address"
|
|
severity="success"
|
|
/>
|
|
<Badge
|
|
v-if="!selectedAddress.isPrimaryAddress && selectedAddress.isServiceAddress"
|
|
value="Service Address"
|
|
severity="secondary"
|
|
/>
|
|
</div>
|
|
|
|
<!-- Service Address Details -->
|
|
<div v-if="selectedAddress.isServiceAddress" class="service-details">
|
|
<div class="detail-item">
|
|
<i class="pi pi-briefcase"></i>
|
|
<span>{{ selectedAddress.projects?.length || 0 }} Projects</span>
|
|
</div>
|
|
<div class="detail-item">
|
|
<i class="pi pi-calendar"></i>
|
|
<span>{{ selectedAddress.onsiteMeetings?.length || 0 }} Bid Meetings</span>
|
|
</div>
|
|
<div v-if="primaryContact" class="detail-item primary-contact">
|
|
<i class="pi pi-user"></i>
|
|
<div class="contact-info">
|
|
<span class="contact-name">{{ primaryContactName }}</span>
|
|
<span class="contact-detail">{{ primaryContactEmail }}</span>
|
|
<span class="contact-detail">{{ primaryContactPhone }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add Address Modal -->
|
|
<Dialog
|
|
:visible="showAddAddressModal"
|
|
@update:visible="showAddAddressModal = $event"
|
|
header="Add Address"
|
|
:modal="true"
|
|
:closable="true"
|
|
class="add-address-dialog"
|
|
>
|
|
<div class="coming-soon">
|
|
<i class="pi pi-hourglass"></i>
|
|
<p>Feature coming soon</p>
|
|
</div>
|
|
<template #footer>
|
|
<Button
|
|
label="Close"
|
|
severity="secondary"
|
|
@click="showAddAddressModal = false"
|
|
/>
|
|
</template>
|
|
</Dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { computed, ref, watch, nextTick } from "vue";
|
|
import { useRoute } from "vue-router";
|
|
import Badge from "primevue/badge";
|
|
import Button from "primevue/button";
|
|
import Dialog from "primevue/dialog";
|
|
import Select from "primevue/select";
|
|
import DataUtils from "../../utils";
|
|
|
|
const route = useRoute();
|
|
const addressParam = route.query.address || null;
|
|
|
|
const props = defineProps({
|
|
addresses: {
|
|
type: Array,
|
|
required: true,
|
|
},
|
|
selectedAddressIdx: {
|
|
type: Number,
|
|
default: 0,
|
|
},
|
|
contacts: {
|
|
type: Array,
|
|
default: () => [],
|
|
},
|
|
});
|
|
|
|
const findAddressIndexByParam = (addressStr) => {
|
|
const trimmedParam = addressStr.trim();
|
|
for (let i = 0; i < props.addresses.length; i++) {
|
|
const addr = props.addresses[i];
|
|
const fullAddr = (addr.fullAddress || DataUtils.calculateFullAddress(addr)).trim();
|
|
if (fullAddr === trimmedParam) {
|
|
return i;
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const emit = defineEmits(["update:selectedAddressIdx"]);
|
|
|
|
const showAddAddressModal = ref(false);
|
|
const selectedAddressIndex = ref(addressParam ? findAddressIndexByParam(addressParam) : props.selectedAddressIdx);
|
|
|
|
// Emit update if the initial index is different from props
|
|
if (addressParam && selectedAddressIndex.value !== null && selectedAddressIndex.value !== props.selectedAddressIdx) {
|
|
nextTick(() => emit("update:selectedAddressIdx", selectedAddressIndex.value));
|
|
}
|
|
|
|
// Watch for external changes to selectedAddressIdx
|
|
watch(() => props.selectedAddressIdx, (newVal) => {
|
|
selectedAddressIndex.value = newVal;
|
|
});
|
|
|
|
// Selected address object
|
|
const selectedAddress = computed(() => {
|
|
if (selectedAddressIndex.value >= 0 && selectedAddressIndex.value < props.addresses.length) {
|
|
return props.addresses[selectedAddressIndex.value];
|
|
}
|
|
return null;
|
|
});
|
|
|
|
// Address options for dropdown
|
|
const addressOptions = computed(() => {
|
|
return props.addresses.map((addr, idx) => ({
|
|
label: addr.fullAddress || DataUtils.calculateFullAddress(addr),
|
|
value: idx,
|
|
addressTitle: addr.fullAddress || 'Unnamed Address',
|
|
isPrimaryAddress: addr.isPrimaryAddress,
|
|
isServiceAddress: addr.isServiceAddress,
|
|
projectCount: addr.projects?.length || 0,
|
|
}));
|
|
});
|
|
|
|
// Primary contact for selected address
|
|
const primaryContact = computed(() => {
|
|
if (!selectedAddress.value?.primaryContact || !props.contacts) return null;
|
|
return props.contacts.find(c => c.name === selectedAddress.value.primaryContact);
|
|
});
|
|
|
|
const primaryContactName = computed(() => {
|
|
if (!primaryContact.value) return "N/A";
|
|
return primaryContact.value.fullName || primaryContact.value.name || "N/A";
|
|
});
|
|
|
|
const primaryContactEmail = computed(() => {
|
|
if (!primaryContact.value) return "N/A";
|
|
return primaryContact.value.emailId || primaryContact.value.customEmail || "N/A";
|
|
});
|
|
|
|
const primaryContactPhone = computed(() => {
|
|
if (!primaryContact.value) return "N/A";
|
|
return primaryContact.value.phone || primaryContact.value.mobileNo || "N/A";
|
|
});
|
|
|
|
// Handle address change
|
|
const handleAddressChange = () => {
|
|
emit("update:selectedAddressIdx", selectedAddressIndex.value);
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.address-selector {
|
|
background: var(--surface-card);
|
|
border-radius: 12px;
|
|
padding: 1.5rem;
|
|
border: 1px solid var(--surface-border);
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.selector-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.selector-header h4 {
|
|
margin: 0;
|
|
font-size: 1.1rem;
|
|
font-weight: 600;
|
|
color: var(--text-color);
|
|
}
|
|
|
|
.address-dropdown {
|
|
width: 100%;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.dropdown-value,
|
|
.dropdown-option {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
padding: 0.25rem 0;
|
|
}
|
|
|
|
.address-title,
|
|
.option-title {
|
|
font-weight: 600;
|
|
color: var(--text-color);
|
|
}
|
|
|
|
.address-badges,
|
|
.option-badges {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.selected-address-info {
|
|
display: flex;
|
|
flex-direction: row;
|
|
gap: 1rem;
|
|
padding: 0.75rem;
|
|
background: var(--surface-ground);
|
|
border-radius: 8px;
|
|
align-items: center;
|
|
}
|
|
|
|
.address-status {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.service-details {
|
|
display: flex;
|
|
flex-direction: row;
|
|
gap: 1rem;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.detail-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.75rem;
|
|
padding: 0.75rem;
|
|
background: var(--surface-card);
|
|
border-radius: 6px;
|
|
}
|
|
|
|
.detail-item i {
|
|
font-size: 1.25rem;
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
.detail-item span {
|
|
font-size: 0.95rem;
|
|
font-weight: 500;
|
|
color: var(--text-color);
|
|
}
|
|
|
|
.detail-item.primary-contact {
|
|
flex: 1;
|
|
}
|
|
|
|
.contact-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.contact-name {
|
|
font-weight: 600;
|
|
color: var(--text-color);
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.contact-detail {
|
|
font-size: 0.875rem;
|
|
color: var(--text-color-secondary);
|
|
}
|
|
|
|
.coming-soon {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
padding: 2rem;
|
|
text-align: center;
|
|
}
|
|
|
|
.coming-soon i {
|
|
font-size: 3rem;
|
|
color: var(--text-color-secondary);
|
|
}
|
|
|
|
.coming-soon p {
|
|
font-size: 1.1rem;
|
|
color: var(--text-color-secondary);
|
|
margin: 0;
|
|
}
|
|
|
|
.w-full {
|
|
width: 100%;
|
|
}
|
|
</style>
|