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

943 lines
31 KiB
Vue

<template>
<div>
<v-card-title class="text-h6 d-flex align-center pb-4">
<v-icon class="mr-2" color="primary">mdi-file-document-edit</v-icon>
EOI Management
</v-card-title>
<v-card-text class="pt-0">
<!-- EOI Documents Section -->
<div v-if="hasEOIDocuments" class="mb-4">
<div class="text-subtitle-1 mb-2">EOI Documents</div>
<div class="d-flex flex-wrap ga-2">
<v-chip
v-for="(doc, index) in eoiDocuments"
:key="index"
color="primary"
variant="tonal"
prepend-icon="mdi-file-pdf-box"
:href="doc.url"
target="_blank"
component="a"
clickable
closable
@click:close="removeDocument(index)"
>
{{ doc.title || `EOI Document ${index + 1}` }}
</v-chip>
</div>
</div>
<!-- Generate EOI Button - Only show if no documents uploaded and no generated EOI -->
<div v-if="!hasGeneratedEOI && !hasEOIDocuments" class="d-flex flex-wrap justify-space-between align-center" :class="mobile ? 'gap-3' : 'gap-4'">
<v-btn
@click="generateEOI"
:loading="isGenerating"
color="success"
variant="flat"
prepend-icon="mdi-file-document-plus"
:size="mobile ? 'default' : 'large'"
class="justify-center"
:class="mobile ? 'flex-grow-1' : ''"
>
Generate EOI
</v-btn>
<v-btn
@click="showUploadDialog = true"
variant="outlined"
color="primary"
prepend-icon="mdi-upload"
:disabled="isUploading"
:size="mobile ? 'default' : 'large'"
:class="mobile ? 'flex-grow-1' : ''"
>
Upload EOI Document
</v-btn>
</div>
<!-- Message when uploaded documents exist -->
<div v-else-if="hasEOIDocuments && !hasGeneratedEOI" class="mb-4">
<v-alert
type="info"
variant="tonal"
class="mb-3"
>
Uploaded EOI documents found. Remove uploaded documents to generate a new EOI.
</v-alert>
<v-btn
@click="showUploadDialog = true"
variant="tonal"
color="success"
prepend-icon="mdi-upload"
:disabled="isUploading"
:size="mobile ? 'default' : 'large'"
>
Upload Additional EOI
</v-btn>
</div>
<!-- Upload EOI Button - Show if generated EOI exists -->
<div v-else-if="hasGeneratedEOI" class="mb-4">
<v-btn
@click="showUploadDialog = true"
variant="tonal"
color="success"
prepend-icon="mdi-upload"
:disabled="isUploading"
:size="mobile ? 'default' : 'large'"
>
Upload Signed EOI
</v-btn>
</div>
<!-- EOI Status Badge -->
<div v-if="hasGeneratedEOI || hasEOIDocuments" class="mb-4 d-flex align-center flex-wrap">
<v-chip
:color="getStatusColor(interest['EOI Status'])"
variant="tonal"
:prepend-icon="!mobile ? 'mdi-file-document-check' : undefined"
:size="mobile ? 'small' : 'default'"
class="mb-1"
>
{{ interest['EOI Status'] }}
</v-chip>
<span v-if="interest['EOI Time Sent']" class="text-caption text-grey-darken-1 ml-3">
{{ mobile ? '' : 'Sent: ' }}{{ formatDate(interest['EOI Time Sent']) }}
</span>
</div>
<!-- Signature Status Section - Only show if EOI is generated but not signed -->
<div v-if="hasGeneratedEOI && !isEOISigned" class="mb-4">
<div class="text-subtitle-2 mb-2 d-flex align-center">
Signature Status
<v-btn
icon="mdi-refresh"
variant="tonal"
size="x-small"
class="ml-2"
color="primary"
@click="checkSignatureStatus"
:loading="isCheckingSignatures"
>
<v-icon size="x-small">mdi-refresh</v-icon>
<v-tooltip activator="parent" location="top">
Refresh signature status
</v-tooltip>
</v-btn>
</div>
<v-list :density="mobile ? 'compact' : 'comfortable'">
<v-list-item class="mb-2">
<template v-slot:prepend>
<v-avatar :color="getSignatureStatusColor('client')" :size="mobile ? 32 : 40">
<v-icon :size="mobile ? 'small' : 'default'">
{{ getSignatureStatusIcon('client') }}
</v-icon>
</v-avatar>
</template>
<v-list-item-title :class="mobile ? 'text-body-2' : ''">
Client Signature
<v-chip
v-if="signatureStatus"
:color="getSignatureStatusColor('client')"
size="x-small"
variant="tonal"
class="ml-2"
>
{{ getSignatureStatusText('client') }}
</v-chip>
</v-list-item-title>
<v-list-item-subtitle :class="mobile ? 'text-caption' : ''">{{ interest['Full Name'] }}</v-list-item-subtitle>
<template v-slot:append>
<div class="d-flex gap-1">
<v-btn
icon="mdi-content-copy"
variant="text"
:size="mobile ? 'small' : 'default'"
@click="copyLink(getClientSignatureUrl, 'client')"
>
<v-icon>mdi-content-copy</v-icon>
<v-tooltip activator="parent" location="top">
Copy signature link
</v-tooltip>
</v-btn>
<v-btn
icon="mdi-open-in-new"
variant="text"
:size="mobile ? 'small' : 'default'"
@click="openLinkInNewTab(getClientSignatureUrl)"
:disabled="!getClientSignatureUrl"
>
<v-icon>mdi-open-in-new</v-icon>
<v-tooltip activator="parent" location="top">
Open in new tab
</v-tooltip>
</v-btn>
</div>
</template>
</v-list-item>
<v-list-item class="mb-2">
<template v-slot:prepend>
<v-avatar :color="getSignatureStatusColor('cc')" :size="mobile ? 32 : 40">
<v-icon :size="mobile ? 'small' : 'default'">
{{ getSignatureStatusIcon('cc') }}
</v-icon>
</v-avatar>
</template>
<v-list-item-title :class="mobile ? 'text-body-2' : ''">
CC Signature
<v-chip
v-if="signatureStatus"
:color="getSignatureStatusColor('cc')"
size="x-small"
variant="tonal"
class="ml-2"
>
{{ getSignatureStatusText('cc') }}
</v-chip>
</v-list-item-title>
<v-list-item-subtitle :class="mobile ? 'text-caption' : ''">Approval</v-list-item-subtitle>
<template v-slot:append>
<div class="d-flex gap-1">
<v-btn
icon="mdi-content-copy"
variant="text"
:size="mobile ? 'small' : 'default'"
@click="copyLink(getCCSignatureUrl, 'cc')"
>
<v-icon>mdi-content-copy</v-icon>
<v-tooltip activator="parent" location="top">
Copy signature link
</v-tooltip>
</v-btn>
<v-btn
icon="mdi-open-in-new"
variant="text"
:size="mobile ? 'small' : 'default'"
@click="openLinkInNewTab(getCCSignatureUrl)"
:disabled="!getCCSignatureUrl"
>
<v-icon>mdi-open-in-new</v-icon>
<v-tooltip activator="parent" location="top">
Open in new tab
</v-tooltip>
</v-btn>
</div>
</template>
</v-list-item>
<v-list-item class="mb-2">
<template v-slot:prepend>
<v-avatar :color="getSignatureStatusColor('developer')" :size="mobile ? 32 : 40">
<v-icon :size="mobile ? 'small' : 'default'">
{{ getSignatureStatusIcon('developer') }}
</v-icon>
</v-avatar>
</template>
<v-list-item-title :class="mobile ? 'text-body-2' : ''">
Developer Signature
<v-chip
v-if="signatureStatus"
:color="getSignatureStatusColor('developer')"
size="x-small"
variant="tonal"
class="ml-2"
>
{{ getSignatureStatusText('developer') }}
</v-chip>
</v-list-item-title>
<v-list-item-subtitle :class="mobile ? 'text-caption' : ''">David Mizrahi</v-list-item-subtitle>
<template v-slot:append>
<div class="d-flex gap-1">
<v-btn
icon="mdi-content-copy"
variant="text"
:size="mobile ? 'small' : 'default'"
@click="copyLink(getDeveloperSignatureUrl, 'developer')"
>
<v-icon>mdi-content-copy</v-icon>
<v-tooltip activator="parent" location="top">
Copy signature link
</v-tooltip>
</v-btn>
<v-btn
icon="mdi-open-in-new"
variant="text"
:size="mobile ? 'small' : 'default'"
@click="openLinkInNewTab(getDeveloperSignatureUrl)"
:disabled="!getDeveloperSignatureUrl"
>
<v-icon>mdi-open-in-new</v-icon>
<v-tooltip activator="parent" location="top">
Open in new tab
</v-tooltip>
</v-btn>
</div>
</template>
</v-list-item>
</v-list>
</div>
<!-- Action Buttons Section -->
<div v-if="hasGeneratedEOI && !isEOISigned" class="mt-4 d-flex flex-wrap gap-2">
<v-btn
@click="showRegenerateConfirmDialog = true"
:loading="isGenerating"
variant="text"
size="small"
prepend-icon="mdi-refresh"
>
Regenerate EOI
</v-btn>
<v-btn
@click="showDeleteGeneratedConfirmDialog = true"
color="error"
variant="outlined"
size="small"
prepend-icon="mdi-delete"
:disabled="signatureStatus?.allSigned"
>
Delete Generated EOI
</v-btn>
</div>
<!-- Delete EOI Button - Only show if EOI is uploaded/signed -->
<div v-if="isEOISigned" class="mt-4">
<v-btn
@click="showDeleteConfirmDialog = true"
color="error"
variant="outlined"
size="small"
prepend-icon="mdi-delete"
>
Delete Uploaded EOI
</v-btn>
</div>
</v-card-text>
<!-- Upload Dialog -->
<v-dialog
v-model="showUploadDialog"
:max-width="mobile ? '100%' : '600'"
:fullscreen="mobile"
:transition="mobile ? 'dialog-bottom-transition' : 'dialog-transition'"
>
<v-card>
<v-card-title class="d-flex align-center">
<v-icon class="mr-2" color="primary">mdi-upload</v-icon>
Upload EOI Document
<v-spacer />
<v-btn icon="mdi-close" variant="text" @click="closeUploadDialog"></v-btn>
</v-card-title>
<v-divider />
<v-card-text :class="mobile ? 'pa-4' : 'pa-6'">
<div class="text-center" :class="mobile ? 'mb-4' : 'mb-6'">
<v-icon :size="mobile ? 60 : 80" color="primary" class="mb-4">mdi-file-pdf-box</v-icon>
<div :class="mobile ? 'text-body-1 mb-2' : 'text-h6 mb-2'">Choose how to upload your EOI</div>
<div :class="mobile ? 'text-caption' : 'text-body-2'" class="text-grey">Upload a signed PDF document</div>
</div>
<v-file-input
v-model="selectedFile"
:label="mobile ? 'Select PDF file' : 'Drop your PDF here or click to browse'"
accept=".pdf"
prepend-icon=""
variant="outlined"
:density="mobile ? 'compact' : 'comfortable'"
:rules="[v => !!v || 'Please select a file']"
:show-size="!mobile"
class="mt-4"
>
<template v-slot:prepend-inner>
<v-icon color="primary" :size="mobile ? 'small' : 'default'">mdi-file-pdf-box</v-icon>
</template>
</v-file-input>
</v-card-text>
<v-divider />
<v-card-actions class="pa-4">
<v-spacer />
<v-btn @click="closeUploadDialog" variant="text" :size="mobile ? 'default' : 'large'">
Cancel
</v-btn>
<v-btn
color="primary"
variant="flat"
@click="handleUpload"
:loading="isUploading"
:disabled="!selectedFile"
:size="mobile ? 'default' : 'large'"
:prepend-icon="!mobile ? 'mdi-upload' : undefined"
>
Upload{{ mobile ? '' : ' EOI' }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Delete Confirmation Dialog for Uploaded Documents -->
<v-dialog
v-model="showDeleteConfirmDialog"
:max-width="mobile ? '100%' : '400'"
:transition="mobile ? 'dialog-bottom-transition' : 'dialog-transition'"
>
<v-card>
<v-card-title class="d-flex align-center">
<v-icon class="mr-2" color="error">mdi-alert</v-icon>
Confirm EOI Deletion
</v-card-title>
<v-card-text>
Are you sure you want to delete the uploaded EOI document? This will:
<ul class="mt-2">
<li>Remove the uploaded EOI document</li>
<li>Reset the Sales Process Level to "Specific Qualified Interest"</li>
<li>Reset the EOI Status to "Awaiting Further Details"</li>
<li>Allow a new EOI to be generated</li>
</ul>
<div class="mt-3 text-error">This action cannot be undone.</div>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
@click="showDeleteConfirmDialog = false"
variant="text"
>
Cancel
</v-btn>
<v-btn
color="error"
variant="flat"
@click="deleteUploadedEOI"
:loading="isDeleting"
>
Delete EOI
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Regenerate Confirmation Dialog -->
<v-dialog
v-model="showRegenerateConfirmDialog"
:max-width="mobile ? '100%' : '500'"
:transition="mobile ? 'dialog-bottom-transition' : 'dialog-transition'"
>
<v-card>
<v-card-title class="d-flex align-center">
<v-icon class="mr-2" color="warning">mdi-alert</v-icon>
Confirm EOI Regeneration
</v-card-title>
<v-card-text>
<v-alert type="warning" variant="tonal" class="mb-4">
<div class="font-weight-medium">Please ensure all information is correct before regenerating.</div>
</v-alert>
Are you sure you want to regenerate the EOI? This will:
<ul class="mt-2">
<li>Delete the current document from Documenso</li>
<li>Remove all existing signature links</li>
<li>Reset the EOI status and Sales Process Level</li>
<li>Generate a new document with current information</li>
</ul>
<div class="mt-3 text-warning">Make sure all berth selections and client information are finalized before proceeding.</div>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
@click="showRegenerateConfirmDialog = false"
variant="text"
>
Cancel
</v-btn>
<v-btn
color="warning"
variant="flat"
@click="confirmRegenerateEOI"
:loading="isGenerating"
>
Regenerate EOI
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Slider Confirmation Dialog for Generated Documents -->
<SliderConfirmation
v-model="showDeleteGeneratedConfirmDialog"
title="Delete Generated EOI"
message="You are about to delete the generated EOI document. This will permanently remove the document from Documenso and reset all signature links."
icon="mdi-delete"
iconColor="error"
confirmButtonText="Delete EOI"
sliderText="Slide to delete generated EOI"
warningText="This action cannot be undone and will require regenerating the document."
:confirmationList="[
'Delete document from Documenso',
'Remove all signature links',
'Reset EOI status to \'Awaiting Further Details\'',
'Reset Sales Process Level to \'Specific Qualified Interest\''
]"
:loading="isDeletingGenerated"
@confirm="deleteGeneratedEOI"
@cancel="showDeleteGeneratedConfirmDialog = false"
/>
</div>
</template>
<script setup lang="ts">
import type { Interest } from '~/utils/types';
const props = defineProps<{
interest: Interest;
}>();
const emit = defineEmits<{
'eoi-generated': [data: { signingLinks: Record<string, string> }];
'update': [];
}>();
const { mobile } = useDisplay();
const toast = useToast();
const isGenerating = ref(false);
const showUploadDialog = ref(false);
const isUploading = ref(false);
const selectedFile = ref<File | null>(null);
const showDeleteConfirmDialog = ref(false);
const showDeleteGeneratedConfirmDialog = ref(false);
const showRegenerateConfirmDialog = ref(false);
const isDeleting = ref(false);
const isDeletingGenerated = ref(false);
const signatureStatus = ref<any>(null);
const isCheckingSignatures = ref(false);
const isValidatingDocument = ref(false);
const documentValidated = ref(false);
const documentExists = ref(true); // Assume true initially
const hasGeneratedEOI = computed(() => {
// Primary check: documensoID must exist for a generated EOI
// If documensoID is null/undefined, then there's no generated EOI regardless of signature links
const documensoID = props.interest['documensoID'];
const hasDocumensoID = !!(documensoID && documensoID !== '' && documensoID !== 'null' && documensoID !== 'undefined');
// If validation has run and found the document doesn't exist, override with local state
const finalResult = documentValidated.value ? (hasDocumensoID && documentExists.value) : hasDocumensoID;
console.log('[EOI Section] hasGeneratedEOI check:', {
documensoID,
hasDocumensoID,
documentValidated: documentValidated.value,
documentExists: documentExists.value,
finalResult,
interestId: props.interest.Id
});
return finalResult;
});
const eoiDocuments = computed(() => {
return props.interest['EOI Document'] || [];
});
const hasEOIDocuments = computed(() => {
return eoiDocuments.value.length > 0;
});
const isEOISigned = computed(() => {
return props.interest['EOI Status'] === 'Signed';
});
// Helper functions to get embedded URLs with fallback to original URLs
const getClientSignatureUrl = computed(() => {
return props.interest['EmbeddedSignatureLinkClient'] || props.interest['Signature Link Client'];
});
const getCCSignatureUrl = computed(() => {
return props.interest['EmbeddedSignatureLinkCC'] || props.interest['Signature Link CC'];
});
const getDeveloperSignatureUrl = computed(() => {
return props.interest['EmbeddedSignatureLinkDeveloper'] || props.interest['Signature Link Developer'];
});
const generateEOI = async (retryCount = 0) => {
isGenerating.value = true;
try {
const response = await $fetch<{
success: boolean;
documentId: string | number;
clientSigningUrl: string;
signingLinks: Record<string, string>;
documensoID?: string;
}>('/api/email/generate-eoi-document', {
method: 'POST',
body: {
interestId: props.interest.Id.toString()
},
// Increase timeout to 60 seconds to handle slow Documenso API calls
timeout: 60000
});
if (response.success) {
toast.success(response.documentId === 'existing'
? 'EOI already exists - signature links retrieved'
: 'EOI generated successfully');
// Update local state immediately to show UI changes
if (response.documensoID) {
// Force update the hasGeneratedEOI computed by setting documentExists
documentExists.value = true;
documentValidated.value = true;
}
emit('eoi-generated', { signingLinks: response.signingLinks });
emit('update'); // Trigger parent to refresh data
// Check signature status after generation
if (response.documentId !== 'existing') {
setTimeout(() => checkSignatureStatus(), 1000);
}
} else {
throw new Error('EOI generation failed');
}
} catch (error: any) {
console.error('Failed to generate EOI:', error);
// Check if it's a timeout error
const isTimeoutError = error.code === 'ECONNABORTED' ||
error.message?.includes('timeout') ||
error.statusCode === 502 ||
error.statusCode === 504;
// Retry logic with better handling for timeouts
if (retryCount < 4 && (isTimeoutError || error.statusCode === 502)) {
const waitTime = Math.min((retryCount + 1) * 2000, 8000); // Exponential backoff, max 8 seconds
console.log(`Retrying EOI generation due to timeout... Attempt ${retryCount + 2}/5 (waiting ${waitTime}ms)`);
toast.info(`Connection timeout. Retrying in ${waitTime/1000} seconds...`);
await new Promise(resolve => setTimeout(resolve, waitTime));
return generateEOI(retryCount + 1);
}
// Show error message after all retries failed
const errorMessage = error.data?.statusMessage || error.message || 'Failed to generate EOI';
if (isTimeoutError) {
toast.error('The server is taking too long to respond. Please try again in a few moments.');
} else {
toast.error(errorMessage);
}
} finally {
isGenerating.value = false;
}
};
const confirmRegenerateEOI = async () => {
showRegenerateConfirmDialog.value = false;
await generateEOI();
};
const checkSignatureStatus = async () => {
if (!props.interest['documensoID']) return;
isCheckingSignatures.value = true;
try {
const response = await $fetch<any>('/api/eoi/check-signature-status', {
params: {
interestId: props.interest.Id.toString()
}
});
if (response.success) {
signatureStatus.value = response;
}
} catch (error: any) {
console.error('Failed to check signature status:', error);
} finally {
isCheckingSignatures.value = false;
}
};
const getSignatureStatusColor = (role: string) => {
if (!signatureStatus.value) return 'grey';
const isSigned = signatureStatus.value.signedRecipients?.some((r: any) => {
if (role === 'client') return r.email === props.interest['Email Address'];
if (role === 'cc') return r.email === 'sales@portnimara.com';
if (role === 'developer') return r.email === 'dm@portnimara.com';
return false;
});
return isSigned ? 'success' : 'warning';
};
const getSignatureStatusIcon = (role: string) => {
if (!signatureStatus.value) return 'mdi-clock-outline';
const isSigned = signatureStatus.value.signedRecipients?.some((r: any) => {
if (role === 'client') return r.email === props.interest['Email Address'];
if (role === 'cc') return r.email === 'sales@portnimara.com';
if (role === 'developer') return r.email === 'dm@portnimara.com';
return false;
});
return isSigned ? 'mdi-check' : 'mdi-clock-outline';
};
const getSignatureStatusText = (role: string) => {
if (!signatureStatus.value) return 'Checking...';
const isSigned = signatureStatus.value.signedRecipients?.some((r: any) => {
if (role === 'client') return r.email === props.interest['Email Address'];
if (role === 'cc') return r.email === 'sales@portnimara.com';
if (role === 'developer') return r.email === 'dm@portnimara.com';
return false;
});
return isSigned ? 'Signed' : 'Pending';
};
const copyLink = async (link: string | undefined, linkType?: 'client' | 'cc' | 'developer') => {
if (!link) return;
try {
await navigator.clipboard.writeText(link);
// Provide specific feedback based on link type
const linkTypeText = linkType ? ` (${linkType === 'cc' ? 'CC' : linkType})` : '';
const isEmbedded = link.includes('portnimara.com/sign/');
toast.success(`${isEmbedded ? 'Embedded' : 'Direct'} signature link${linkTypeText} copied to clipboard`);
// Update EOI Time Sent if not already set
if (!props.interest['EOI Time Sent']) {
try {
await $fetch('/api/update-interest', {
method: 'POST',
body: {
id: props.interest.Id.toString(),
data: {
'EOI Time Sent': new Date().toISOString()
}
}
});
emit('update'); // Refresh parent data
} catch (error) {
console.error('Failed to update EOI Time Sent:', error);
}
}
} catch (error) {
toast.error('Failed to copy link');
}
};
const openLinkInNewTab = (link: string | undefined) => {
if (!link) return;
try {
window.open(link, '_blank', 'noopener,noreferrer');
} catch (error) {
console.error('Failed to open link:', error);
toast.error('Failed to open link in new tab');
}
};
const formatDate = (dateString: string) => {
if (!dateString) return '';
try {
const date = new Date(dateString);
// Check if date is valid
if (isNaN(date.getTime())) {
return dateString; // Return original if parsing fails
}
return date.toLocaleString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: false
});
} catch (error) {
console.error('Date formatting error:', error);
return dateString;
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'Waiting for Signatures':
return 'warning';
case 'Signed':
return 'success';
default:
return 'grey';
}
};
const removeDocument = async (index: number) => {
// For now, we'll just show a message since removing documents
// would require updating the database
toast.warning('Document removal is not yet implemented');
};
const uploadEOI = async (file: File) => {
isUploading.value = true;
try {
const formData = new FormData();
formData.append('file', file);
const response = await $fetch<{ success: boolean; document: any; message: string }>('/api/eoi/upload-document', {
method: 'POST',
query: {
interestId: props.interest.Id.toString()
},
body: formData
});
if (response.success) {
toast.success('EOI document uploaded successfully');
showUploadDialog.value = false;
selectedFile.value = null; // Reset file selection
emit('update'); // Refresh parent data
}
} catch (error: any) {
console.error('Failed to upload EOI:', error);
toast.error(error.data?.statusMessage || error.message || 'Failed to upload EOI document');
} finally {
isUploading.value = false;
}
};
const handleUpload = async () => {
if (!selectedFile.value) return;
await uploadEOI(selectedFile.value);
};
const closeUploadDialog = () => {
showUploadDialog.value = false;
selectedFile.value = null;
};
const deleteUploadedEOI = async () => {
isDeleting.value = true;
try {
const response = await $fetch<{ success: boolean; message: string }>('/api/eoi/delete-document', {
method: 'POST',
body: {
interestId: props.interest.Id.toString()
}
});
if (response.success) {
toast.success('EOI document deleted successfully');
showDeleteConfirmDialog.value = false;
emit('update'); // Refresh parent data
}
} catch (error: any) {
console.error('Failed to delete EOI:', error);
toast.error(error.data?.statusMessage || error.message || 'Failed to delete EOI document');
} finally {
isDeleting.value = false;
}
};
const deleteGeneratedEOI = async () => {
isDeletingGenerated.value = true;
try {
const response = await $fetch<{ success: boolean; message: string }>('/api/eoi/delete-generated-document', {
method: 'POST',
body: {
interestId: props.interest.Id.toString()
}
});
if (response.success) {
toast.success('Generated EOI deleted successfully');
showDeleteGeneratedConfirmDialog.value = false;
emit('update'); // Refresh parent data
}
} catch (error: any) {
console.error('Failed to delete generated EOI:', error);
toast.error(error.data?.statusMessage || error.message || 'Failed to delete generated EOI');
} finally {
isDeletingGenerated.value = false;
}
};
const validateDocument = async () => {
if (!props.interest['documensoID']) {
documentValidated.value = true;
documentExists.value = false;
return;
}
isValidatingDocument.value = true;
try {
console.log('[EOI Section] Validating document for interest:', props.interest.Id);
const response = await $fetch<{
success: boolean;
valid: boolean;
cleaned: boolean;
message: string;
documentStatus?: string;
}>('/api/eoi/validate-document', {
params: {
interestId: props.interest.Id.toString()
}
});
if (response.success) {
documentValidated.value = true;
documentExists.value = response.valid && !response.cleaned;
if (response.cleaned) {
console.log('[EOI Section] Document validation cleaned up orphaned records');
toast.info('Detected and cleaned up orphaned EOI data');
emit('update'); // Refresh parent to reflect cleaned state
} else {
console.log('[EOI Section] Document validation passed');
}
}
} catch (error: any) {
console.error('[EOI Section] Failed to validate document:', error);
documentValidated.value = true;
documentExists.value = true; // Assume exists if validation fails
// Don't show error to user - validation is a background process
} finally {
isValidatingDocument.value = false;
}
};
// Check signature status on mount if EOI is generated
onMounted(() => {
if (hasGeneratedEOI.value) {
// First validate the document exists
validateDocument().then(() => {
// Only check signature status if document is still valid after validation
if (hasGeneratedEOI.value && !isEOISigned.value) {
checkSignatureStatus();
}
});
}
});
</script>