port-nimara-client-portal/components/InterestDetailsModal.vue

1100 lines
36 KiB
Vue

<template>
<v-dialog
v-model="isOpen"
fullscreen
hide-overlay
transition="dialog-bottom-transition"
>
<v-card>
<v-toolbar dark color="primary">
<v-btn icon dark @click="closeModal">
<v-icon>mdi-close</v-icon>
</v-btn>
<v-toolbar-title>
<v-icon class="mr-2">mdi-account-details</v-icon>
Interest Details
</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-items v-if="!mobile">
<v-btn
@click="requestMoreInfoToSales"
variant="text"
:loading="isRequestingMoreInfo"
:disabled="
isRequestingMoreInfo ||
isRequestingMoreInformation ||
isSendingEOI ||
isDeleting
"
>
<v-icon start>mdi-information-outline</v-icon>
Info to Sales
</v-btn>
<v-btn
@click="requestMoreInformation"
variant="text"
:loading="isRequestingMoreInformation"
:disabled="
isRequestingMoreInfo ||
isRequestingMoreInformation ||
isSendingEOI ||
isDeleting
"
>
<v-icon start>mdi-email-outline</v-icon>
Request Info
</v-btn>
<v-btn
@click="eoiSendToSales"
variant="text"
:loading="isSendingEOI"
:disabled="
isRequestingMoreInfo ||
isRequestingMoreInformation ||
isSendingEOI ||
isDeleting
"
>
<v-icon start>mdi-send</v-icon>
EOI to Sales
</v-btn>
<v-btn
@click="confirmDelete"
variant="flat"
color="error"
:loading="isDeleting"
:disabled="
isRequestingMoreInfo ||
isRequestingMoreInformation ||
isSendingEOI ||
isSaving ||
isDeleting
"
class="ml-4"
>
<v-icon start color="white">mdi-delete</v-icon>
<span class="text-white">Delete</span>
</v-btn>
<v-btn
variant="flat"
color="success"
size="large"
@click="saveInterest"
:loading="isSaving"
:disabled="isSaving || isDeleting"
class="ml-2"
>
<v-icon start>mdi-content-save</v-icon>
Save Changes
</v-btn>
</v-toolbar-items>
</v-toolbar>
<v-card-text v-if="interest">
<v-stepper
v-show="!mobile"
v-model="currentStep"
class="mb-6"
variant="flat"
alt-labels
elevation="0"
bg-color="transparent"
>
<v-stepper-header>
<template
v-for="(level, index) in InterestSalesProcessLevelFlow"
:key="index"
>
<v-stepper-item
:step="index"
:complete="currentStep + 1 > index"
:title="level"
:color="currentStep === index ? getSalesLevelColor(level) : (currentStep + 1 > index ? getSalesLevelColor(level) : 'grey')"
:icon="currentStep + 1 > index ? 'mdi-check' : undefined"
/>
<v-divider
v-if="index < InterestSalesProcessLevelFlow.length - 1"
></v-divider>
</template>
</v-stepper-header>
</v-stepper>
<!-- Non-editable fields as informational badges -->
<v-row class="mb-4">
<v-col cols="12">
<div class="d-flex flex-wrap ga-2">
<v-chip
v-if="interest['Created At']"
color="info"
variant="tonal"
prepend-icon="mdi-calendar-clock"
>
Created: {{ formatDate(interest["Created At"]) }}
</v-chip>
<v-chip
v-if="interest['Request Form Sent']"
color="success"
variant="tonal"
prepend-icon="mdi-email-check"
>
Request Form Sent:
{{ formatDate(interest["Request Form Sent"]) }}
</v-chip>
<v-chip
v-if="interest['EOI Time Sent']"
color="warning"
variant="tonal"
prepend-icon="mdi-email-fast"
>
EOI Sent: {{ formatDate(interest["EOI Time Sent"]) }}
</v-chip>
<v-chip
v-if="interest['Time LOI Sent']"
color="secondary"
variant="tonal"
prepend-icon="mdi-file-document-check"
>
LOI Sent: {{ formatDate(interest["Time LOI Sent"]) }}
</v-chip>
</div>
</v-col>
</v-row>
<!-- Mobile Action Buttons -->
<v-card v-if="mobile" variant="flat" class="mb-6">
<v-card-title class="text-h6 d-flex align-center pb-4">
<v-icon class="mr-2" color="primary">mdi-gesture-tap</v-icon>
Actions
</v-card-title>
<v-card-text class="pt-2">
<v-row dense>
<v-col cols="6">
<v-btn
@click="requestMoreInfoToSales"
variant="outlined"
color="primary"
block
:loading="isRequestingMoreInfo"
:disabled="
isRequestingMoreInfo ||
isRequestingMoreInformation ||
isSendingEOI
"
class="mb-2 d-flex flex-column"
size="large"
>
<v-icon size="large" class="mb-1">mdi-information-outline</v-icon>
<span class="text-caption">Info to Sales</span>
</v-btn>
</v-col>
<v-col cols="6">
<v-btn
@click="requestMoreInformation"
variant="outlined"
color="primary"
block
:loading="isRequestingMoreInformation"
:disabled="
isRequestingMoreInfo ||
isRequestingMoreInformation ||
isSendingEOI
"
class="mb-2 d-flex flex-column"
size="large"
>
<v-icon size="large" class="mb-1">mdi-email-outline</v-icon>
<span class="text-caption">Request Info</span>
</v-btn>
</v-col>
<v-col cols="6">
<v-btn
@click="eoiSendToSales"
variant="outlined"
color="primary"
block
:loading="isSendingEOI"
:disabled="
isRequestingMoreInfo ||
isRequestingMoreInformation ||
isSendingEOI
"
class="mb-2 d-flex flex-column"
size="large"
>
<v-icon size="large" class="mb-1">mdi-send</v-icon>
<span class="text-caption">EOI to Sales</span>
</v-btn>
</v-col>
<v-col cols="6">
<v-btn
@click="saveInterest"
variant="flat"
color="success"
block
:loading="isSaving"
:disabled="isSaving"
class="mb-2 d-flex flex-column"
size="large"
>
<v-icon size="large" class="mb-1">mdi-content-save</v-icon>
<span class="text-caption">Save Changes</span>
</v-btn>
</v-col>
</v-row>
</v-card-text>
</v-card>
<v-form @submit.prevent="saveInterest">
<!-- Contact Information Section -->
<v-card variant="flat" class="mb-6">
<v-card-title class="text-h6 d-flex align-center pb-4">
<v-icon class="mr-2" color="primary">mdi-account-circle</v-icon>
Contact Information
</v-card-title>
<v-card-text class="pt-2">
<v-row dense>
<v-col cols="12" md="4">
<v-text-field
v-model="interest['Full Name']"
label="Full Name"
variant="outlined"
density="comfortable"
prepend-inner-icon="mdi-account"
></v-text-field>
</v-col>
<v-col cols="12" md="4">
<v-text-field
v-model="interest['Email Address']"
label="Email Address"
variant="outlined"
density="comfortable"
prepend-inner-icon="mdi-email"
></v-text-field>
</v-col>
<v-col cols="12" md="4">
<PhoneInput
v-model="interest['Phone Number']"
label="Phone Number"
variant="outlined"
density="comfortable"
default-country="US"
:preferred-countries="['US', 'FR', 'ES', 'PT', 'GB']"
/>
</v-col>
<v-col cols="12" md="4">
<v-text-field
v-model="interest.Address"
label="Address"
variant="outlined"
density="comfortable"
prepend-inner-icon="mdi-map-marker"
></v-text-field>
</v-col>
<v-col cols="12" md="4">
<v-text-field
v-model="interest['Place of Residence']"
label="Place of Residence"
variant="outlined"
density="comfortable"
prepend-inner-icon="mdi-home"
></v-text-field>
</v-col>
<v-col cols="12" md="4">
<v-select
v-model="interest['Contact Method Preferred']"
label="Contact Method Preferred"
variant="outlined"
density="comfortable"
:items="ContactMethodPreferredFlow"
prepend-inner-icon="mdi-message-text"
></v-select>
</v-col>
</v-row>
</v-card-text>
</v-card>
<!-- Yacht Information Section -->
<v-card variant="flat" class="mb-6">
<v-card-title class="text-h6 d-flex align-center pb-4">
<v-icon class="mr-2" color="primary">mdi-sail-boat</v-icon>
Yacht Information
</v-card-title>
<v-card-text class="pt-2">
<v-row dense>
<v-col cols="12" md="3">
<v-text-field
v-model="interest['Yacht Name']"
label="Yacht Name"
variant="outlined"
density="comfortable"
prepend-inner-icon="mdi-ferry"
></v-text-field>
</v-col>
<v-col cols="12" md="3">
<v-text-field
v-model="interest.Length"
label="Length"
variant="outlined"
density="comfortable"
prepend-inner-icon="mdi-ruler"
></v-text-field>
</v-col>
<v-col cols="12" md="3">
<v-text-field
v-model="interest.Width"
label="Width"
variant="outlined"
density="comfortable"
prepend-inner-icon="mdi-arrow-expand-horizontal"
></v-text-field>
</v-col>
<v-col cols="12" md="3">
<v-text-field
v-model="interest.Depth"
label="Depth"
variant="outlined"
density="comfortable"
prepend-inner-icon="mdi-arrow-expand-vertical"
></v-text-field>
</v-col>
</v-row>
</v-card-text>
</v-card>
<!-- Sales Pipeline Status Section -->
<v-card variant="flat" class="mb-6">
<v-card-title class="text-h6 d-flex align-center pb-4">
<v-icon class="mr-2" color="primary">mdi-chart-timeline-variant</v-icon>
Sales Pipeline Status
</v-card-title>
<v-card-text class="pt-2">
<v-row dense>
<v-col cols="12">
<v-select
v-model="interest['Sales Process Level']"
label="Current Sales Level"
variant="outlined"
density="comfortable"
:items="InterestSalesProcessLevelFlow"
prepend-inner-icon="mdi-chart-line"
>
<template v-slot:selection="{ item }">
<v-chip
:color="getSalesLevelColor(item.value)"
size="small"
class="text-white"
>
{{ item.value }}
</v-chip>
</template>
<template v-slot:item="{ item, props }">
<v-list-item
v-bind="props"
:title="item.value"
>
<template v-slot:prepend>
<v-icon :color="getSalesLevelColor(item.value)">
mdi-circle
</v-icon>
</template>
<template v-slot:title>
<span :style="{ color: getSalesLevelColor(item.value) }">
{{ item.value }}
</span>
</template>
</v-list-item>
</template>
</v-select>
<div class="mt-4">
<v-progress-linear
:model-value="(currentStep + 1) / InterestSalesProcessLevelFlow.length * 100"
height="10"
rounded
color="blue"
/>
<div class="text-caption text-center mt-2">
Progress: {{ currentStep + 1 }} of {{ InterestSalesProcessLevelFlow.length }} stages
</div>
</div>
</v-col>
</v-row>
</v-card-text>
</v-card>
<!-- Berth & Sales Information Section -->
<v-card variant="flat" class="mb-6">
<v-card-title class="text-h6 d-flex align-center pb-4">
<v-icon class="mr-2" color="primary">mdi-anchor</v-icon>
Berth Information
</v-card-title>
<v-card-text class="pt-2">
<v-row dense>
<v-col cols="12" md="4">
<v-text-field
v-model="interest['Berth Size Desired']"
label="Berth Size Desired"
variant="outlined"
density="comfortable"
prepend-inner-icon="mdi-tape-measure"
></v-text-field>
</v-col>
<v-col cols="12" md="4">
<v-select
v-model="interest['Lead Category']"
label="Lead Category"
variant="outlined"
density="comfortable"
:items="InterestLeadCategoryFlow"
prepend-inner-icon="mdi-tag"
></v-select>
</v-col>
<v-col cols="12" md="6">
<v-autocomplete
v-model="selectedBerthRecommendations"
:items="availableBerths"
:item-title="(item) => item['Mooring Number']"
:item-value="(item) => item.Id"
label="Berth Recommendations"
variant="outlined"
density="comfortable"
multiple
chips
closable-chips
:loading="loadingBerths"
prepend-inner-icon="mdi-star"
@update:model-value="updateBerthRecommendations"
>
<template v-slot:chip="{ props, item }">
<v-chip
v-bind="props"
:text="item.raw['Mooring Number']"
size="small"
></v-chip>
</template>
</v-autocomplete>
</v-col>
<v-col cols="12" md="6">
<v-autocomplete
v-model="selectedBerths"
:items="availableBerths"
:item-title="(item) => item['Mooring Number']"
:item-value="(item) => item.Id"
label="Berths"
variant="outlined"
density="comfortable"
multiple
chips
closable-chips
:loading="loadingBerths"
prepend-inner-icon="mdi-dock-window"
@update:model-value="updateBerths"
>
<template v-slot:chip="{ props, item }">
<v-chip
v-bind="props"
:text="item.raw['Mooring Number']"
size="small"
></v-chip>
</template>
</v-autocomplete>
</v-col>
</v-row>
</v-card-text>
</v-card>
<!-- Process Status Section -->
<v-card variant="flat" class="mb-6">
<v-card-title class="text-h6 d-flex align-center pb-4">
<v-icon class="mr-2" color="primary">mdi-progress-check</v-icon>
Process Status
</v-card-title>
<v-card-text class="pt-2">
<v-row dense>
<v-col cols="12">
<v-row dense>
<v-col cols="12" md="2">
<v-select
v-model="interest['EOI Status']"
label="EOI Status"
variant="outlined"
density="comfortable"
:items="EOIStatusFlow"
prepend-inner-icon="mdi-file-document-outline"
></v-select>
</v-col>
<v-col cols="12" md="2">
<v-select
v-model="interest['Berth Info Sent Status']"
label="Berth Info Sent"
variant="outlined"
density="comfortable"
:items="BerthInfoSentStatusFlow"
prepend-inner-icon="mdi-information-outline"
></v-select>
</v-col>
<v-col cols="12" md="2">
<v-select
v-model="interest['Contract Sent Status']"
label="Contract Sent"
variant="outlined"
density="comfortable"
:items="ContractSentStatusFlow"
prepend-inner-icon="mdi-email-send"
></v-select>
</v-col>
<v-col cols="12" md="3">
<v-select
v-model="interest['Deposit 10% Status']"
label="Deposit 10%"
variant="outlined"
density="comfortable"
:items="Deposit10PercentStatusFlow"
prepend-inner-icon="mdi-cash"
></v-select>
</v-col>
<v-col cols="12" md="3">
<v-select
v-model="interest['Contract Status']"
label="Contract Status"
variant="outlined"
density="comfortable"
:items="ContractStatusFlow"
prepend-inner-icon="mdi-file-document"
></v-select>
</v-col>
</v-row>
</v-col>
</v-row>
</v-card-text>
</v-card>
<!-- Additional Information Section -->
<v-card variant="flat" class="mb-6">
<v-card-title class="text-h6 d-flex align-center pb-4">
<v-icon class="mr-2" color="primary">mdi-information</v-icon>
Additional Information
</v-card-title>
<v-card-text class="pt-2">
<v-row dense>
<v-col cols="12" md="4">
<v-text-field
v-model="interest.Source"
label="Source"
variant="outlined"
density="comfortable"
prepend-inner-icon="mdi-source-branch"
></v-text-field>
</v-col>
<v-col cols="12" md="8">
<v-text-field
v-model="interest['Extra Comments']"
label="Extra Comments"
variant="outlined"
density="comfortable"
prepend-inner-icon="mdi-comment-text"
></v-text-field>
</v-col>
<v-col
cols="12"
v-if="
interest['EOI Document'] &&
interest['EOI Document'].length > 0
"
>
<div class="d-flex align-center">
<v-icon class="mr-2" color="primary"
>mdi-file-document</v-icon
>
<span class="text-subtitle-1 mr-3">EOI Documents:</span>
<div class="d-flex flex-wrap ga-2">
<v-chip
v-for="(doc, index) in interest['EOI Document']"
:key="index"
color="primary"
variant="tonal"
prepend-icon="mdi-file-pdf-box"
:href="doc.url"
target="_blank"
component="a"
clickable
>
{{ doc.title || `EOI Document ${index + 1}` }}
</v-chip>
</div>
</div>
</v-col>
</v-row>
</v-card-text>
</v-card>
<!-- Email Communication Section -->
<EmailCommunication
v-if="interest"
:interest="interest"
/>
</v-form>
</v-card-text>
</v-card>
</v-dialog>
</template>
<script lang="ts" setup>
import { ref, computed, watch, onMounted } from "vue";
import type { Interest, Berth } from "@/utils/types";
import PhoneInput from "./PhoneInput.vue";
import EmailCommunication from "./EmailCommunication.vue";
import {
InterestSalesProcessLevelFlow,
InterestLeadCategoryFlow,
ContactMethodPreferredFlow,
EOIStatusFlow,
BerthInfoSentStatusFlow,
ContractSentStatusFlow,
Deposit10PercentStatusFlow,
ContractStatusFlow,
} from "@/utils/types";
interface Props {
modelValue: boolean;
selectedInterest: Interest | null;
}
interface Emits {
(e: "update:modelValue", value: boolean): void;
(e: "save", interest: Interest): void;
(e: "requestMoreInfoToSales", interest: Interest): void;
(e: "requestMoreInformation", interest: Interest): void;
(e: "eoiSendToSales", interest: Interest): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const { mobile } = useDisplay();
const user = useDirectusUser();
const toast = useToast();
// Local copy of the interest for editing
const interest = ref<Interest | null>(null);
// Loading states for buttons
const isSaving = ref(false);
const isRequestingMoreInfo = ref(false);
const isRequestingMoreInformation = ref(false);
const isSendingEOI = ref(false);
const isDeleting = ref(false);
// Berths related data
const availableBerths = ref<Berth[]>([]);
const loadingBerths = ref(false);
const selectedBerths = ref<number[]>([]);
const selectedBerthRecommendations = ref<number[]>([]);
const originalBerths = ref<number[]>([]);
const originalBerthRecommendations = ref<number[]>([]);
// Sync the local copy with the prop
watch(
() => props.selectedInterest,
async (newInterest) => {
if (newInterest) {
interest.value = { ...newInterest };
// Load linked berths and recommendations
await loadLinkedBerths();
} else {
interest.value = null;
selectedBerths.value = [];
selectedBerthRecommendations.value = [];
originalBerths.value = [];
originalBerthRecommendations.value = [];
}
},
{ immediate: true }
);
// Computed property for v-model binding
const isOpen = computed({
get: () => props.modelValue,
set: (value: boolean) => emit("update:modelValue", value),
});
const currentStep = computed(() => {
return InterestSalesProcessLevelFlow.indexOf(
interest.value?.["Sales Process Level"] || ""
);
});
const closeModal = () => {
isOpen.value = false;
};
const saveInterest = async () => {
if (interest.value) {
isSaving.value = true;
try {
// Create a copy of the interest data without the Berths and Berth Recommendations fields
const dataToSave = { ...interest.value };
delete (dataToSave as any).Berths;
delete (dataToSave as any)["Berth Recommendations"];
// Call the update-interest API
await $fetch("/api/update-interest", {
method: "POST",
headers: {
"x-tag": user.value?.email ? "094ut234" : "pjnvü1230",
},
body: {
id: interest.value.Id.toString(),
data: dataToSave,
},
});
toast.success("Interest saved successfully!");
emit("save", interest.value);
closeModal();
} catch (error) {
console.error("Failed to save interest:", error);
toast.error("Failed to save interest. Please try again.");
} finally {
isSaving.value = false;
}
}
};
const requestMoreInfoToSales = async () => {
if (interest.value) {
isRequestingMoreInfo.value = true;
try {
// Call the request-more-info-to-sales API
await $fetch("/api/request-more-info-to-sales", {
method: "POST",
headers: {
"x-tag": user.value?.email ? "094ut234" : "pjnvü1230",
},
body: {
interestId: interest.value.Id.toString(),
},
});
toast.success("Request More Info - To Sales sent successfully!");
emit("requestMoreInfoToSales", interest.value);
} catch (error) {
console.error("Failed to send request:", error);
toast.error("Failed to send request. Please try again.");
} finally {
isRequestingMoreInfo.value = false;
}
}
};
const requestMoreInformation = async () => {
if (interest.value) {
isRequestingMoreInformation.value = true;
try {
// Call the request-more-information API
await $fetch("/api/request-more-information", {
method: "POST",
headers: {
"x-tag": user.value?.email ? "094ut234" : "pjnvü1230",
},
body: {
interestId: interest.value.Id.toString(),
},
});
toast.success("Request More Information sent successfully!");
emit("requestMoreInformation", interest.value);
} catch (error) {
console.error("Failed to send request:", error);
toast.error("Failed to send request. Please try again.");
} finally {
isRequestingMoreInformation.value = false;
}
}
};
const eoiSendToSales = async () => {
if (interest.value) {
isSendingEOI.value = true;
try {
// Call the eoi-send-to-sales API
await $fetch("/api/eoi-send-to-sales", {
method: "POST",
headers: {
"x-tag": user.value?.email ? "094ut234" : "pjnvü1230",
},
body: {
interestId: interest.value.Id.toString(),
},
});
toast.success("EOI Send to Sales sent successfully!");
emit("eoiSendToSales", interest.value);
} catch (error) {
console.error("Failed to send EOI:", error);
toast.error("Failed to send EOI. Please try again.");
} finally {
isSendingEOI.value = false;
}
}
};
// Load all available berths
const loadAvailableBerths = async () => {
loadingBerths.value = true;
try {
const response = await $fetch<{ list: Berth[] }>("/api/get-berths", {
headers: {
"x-tag": user.value?.email ? "094ut234" : "pjnvü1230",
},
});
availableBerths.value = response.list || [];
} catch (error) {
console.error("Failed to load berths:", error);
} finally {
loadingBerths.value = false;
}
};
// Load linked berths for the current interest
const loadLinkedBerths = async () => {
if (!interest.value) return;
try {
// Load berths
const berthsResponse = await $fetch<{ list: Array<{ Id: number }> }>(
"/api/get-interest-berths",
{
headers: {
"x-tag": user.value?.email ? "094ut234" : "pjnvü1230",
},
params: {
interestId: interest.value.Id,
linkType: "berths",
},
}
);
// Load berth recommendations
const recommendationsResponse = await $fetch<{
list: Array<{ Id: number }>;
}>("/api/get-interest-berths", {
headers: {
"x-tag": user.value?.email ? "094ut234" : "pjnvü1230",
},
params: {
interestId: interest.value.Id,
linkType: "recommendations",
},
});
selectedBerths.value = (berthsResponse.list || []).map((b) => b.Id);
selectedBerthRecommendations.value = (
recommendationsResponse.list || []
).map((b) => b.Id);
// Store original values to detect changes
originalBerths.value = [...selectedBerths.value];
originalBerthRecommendations.value = [
...selectedBerthRecommendations.value,
];
} catch (error) {
console.error("Failed to load linked berths:", error);
}
};
// Update berths when selection changes
const updateBerths = async (newBerths: number[]) => {
if (!interest.value) return;
try {
// Find berths to add and remove
const toAdd = newBerths.filter((id) => !originalBerths.value.includes(id));
const toRemove = originalBerths.value.filter(
(id) => !newBerths.includes(id)
);
// Link new berths
if (toAdd.length > 0) {
await $fetch("/api/link-berths-to-interest", {
method: "POST",
headers: {
"x-tag": user.value?.email ? "094ut234" : "pjnvü1230",
},
body: {
interestId: interest.value.Id,
berthIds: toAdd,
},
});
}
// Unlink removed berths
if (toRemove.length > 0) {
await $fetch("/api/unlink-berths-from-interest", {
method: "POST",
headers: {
"x-tag": user.value?.email ? "094ut234" : "pjnvü1230",
},
body: {
interestId: interest.value.Id,
berthIds: toRemove,
},
});
}
// Update original values
originalBerths.value = [...newBerths];
} catch (error) {
console.error("Failed to update berths:", error);
toast.error("Failed to update berths. Please try again.");
// Revert to original values on error
selectedBerths.value = [...originalBerths.value];
}
};
// Update berth recommendations when selection changes
const updateBerthRecommendations = async (newRecommendations: number[]) => {
if (!interest.value) return;
try {
// Find recommendations to add and remove
const toAdd = newRecommendations.filter(
(id) => !originalBerthRecommendations.value.includes(id)
);
const toRemove = originalBerthRecommendations.value.filter(
(id) => !newRecommendations.includes(id)
);
// Link new recommendations
if (toAdd.length > 0) {
await $fetch("/api/link-berth-recommendations-to-interest", {
method: "POST",
headers: {
"x-tag": user.value?.email ? "094ut234" : "pjnvü1230",
},
body: {
interestId: interest.value.Id,
berthIds: toAdd,
},
});
}
// Unlink removed recommendations
if (toRemove.length > 0) {
await $fetch("/api/unlink-berth-recommendations-from-interest", {
method: "POST",
headers: {
"x-tag": user.value?.email ? "094ut234" : "pjnvü1230",
},
body: {
interestId: interest.value.Id,
berthIds: toRemove,
},
});
}
// Update original values
originalBerthRecommendations.value = [...newRecommendations];
} catch (error) {
console.error("Failed to update berth recommendations:", error);
toast.error("Failed to update berth recommendations. Please try again.");
// Revert to original values on error
selectedBerthRecommendations.value = [
...originalBerthRecommendations.value,
];
}
};
// Format date helper function
const formatDate = (dateString: string | null | undefined) => {
if (!dateString) return "";
try {
// Handle DD-MM-YYYY format
if (dateString.includes("-")) {
const parts = dateString.split("-");
if (parts.length === 3) {
const [day, month, year] = parts;
const date = new Date(
parseInt(year),
parseInt(month) - 1,
parseInt(day)
);
return date.toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
});
}
}
// Fallback to direct date parsing
const date = new Date(dateString);
if (!isNaN(date.getTime())) {
return date.toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
});
}
return dateString;
} catch (error) {
return dateString;
}
};
// Get color for sales level - matching InterestSalesBadge.vue
const getSalesLevelColor = (level: string) => {
const colorMapping = {
"General Qualified Interest": "blue",
"Specific Qualified Interest": "green",
"LOI and NDA Sent": "orange",
"Signed LOI and NDA": "purple",
"Made Reservation": "pink",
"Contract Negotiation": "brown",
"Contract Negotiations Finalized": "teal",
"Contract Signed": "indigo",
};
return colorMapping[level as keyof typeof colorMapping] || "grey";
};
// Confirm delete
const confirmDelete = () => {
if (!interest.value) return;
if (confirm(`Are you sure you want to delete the interest for ${interest.value['Full Name']}? This action cannot be undone.`)) {
deleteInterest();
}
};
// Delete interest
const deleteInterest = async () => {
if (!interest.value) return;
isDeleting.value = true;
try {
await $fetch("/api/delete-interest", {
method: "POST",
headers: {
"x-tag": user.value?.email ? "094ut234" : "pjnvü1230",
},
body: {
id: interest.value.Id.toString(),
},
});
toast.success("Interest deleted successfully!");
closeModal();
emit("save", interest.value); // Trigger refresh
} catch (error) {
console.error("Failed to delete interest:", error);
toast.error("Failed to delete interest. Please try again.");
} finally {
isDeleting.value = false;
}
};
// Load berths when component mounts
onMounted(() => {
loadAvailableBerths();
});
</script>