Merge branch 'main' of https://code.portnimara.com/ron/client-portal
This commit is contained in:
commit
66729dfad6
|
|
@ -1,3 +1,873 @@
|
|||
<<<<<<< HEAD
|
||||
=======
|
||||
<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 gap-2">
|
||||
<v-btn
|
||||
@click="generateEOI"
|
||||
:loading="isGenerating"
|
||||
color="success"
|
||||
variant="flat"
|
||||
prepend-icon="mdi-file-document-plus"
|
||||
:size="mobile ? 'default' : 'large'"
|
||||
: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' : ''">Oscar Faragher</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>
|
||||
|
||||
<!-- Debug Section -->
|
||||
<div v-if="hasGeneratedEOI" class="mt-4">
|
||||
<v-expansion-panels variant="accordion">
|
||||
<v-expansion-panel title="Debug: Embedded URLs">
|
||||
<v-expansion-panel-text>
|
||||
<div class="text-caption text-grey">
|
||||
<strong>Embedded URL Debug Information:</strong>
|
||||
</div>
|
||||
<v-table density="compact" class="mt-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Embedded URL</th>
|
||||
<th>Original URL</th>
|
||||
<th>Final URL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Client</strong></td>
|
||||
<td class="text-caption">{{ debugEmbeddedUrls.client.embedded || 'None' }}</td>
|
||||
<td class="text-caption">{{ debugEmbeddedUrls.client.original || 'None' }}</td>
|
||||
<td class="text-caption">{{ debugEmbeddedUrls.client.final || 'None' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>CC</strong></td>
|
||||
<td class="text-caption">{{ debugEmbeddedUrls.cc.embedded || 'None' }}</td>
|
||||
<td class="text-caption">{{ debugEmbeddedUrls.cc.original || 'None' }}</td>
|
||||
<td class="text-caption">{{ debugEmbeddedUrls.cc.final || 'None' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Developer</strong></td>
|
||||
<td class="text-caption">{{ debugEmbeddedUrls.developer.embedded || 'None' }}</td>
|
||||
<td class="text-caption">{{ debugEmbeddedUrls.developer.original || 'None' }}</td>
|
||||
<td class="text-caption">{{ debugEmbeddedUrls.developer.final || 'None' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons Section -->
|
||||
<div v-if="hasGeneratedEOI && !isEOISigned" class="mt-4 d-flex flex-wrap gap-2">
|
||||
<v-btn
|
||||
@click="generateEOI"
|
||||
: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>
|
||||
|
||||
<!-- 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 isDeleting = ref(false);
|
||||
const isDeletingGenerated = ref(false);
|
||||
const signatureStatus = ref<any>(null);
|
||||
const isCheckingSignatures = ref(false);
|
||||
|
||||
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
|
||||
|
||||
// DEBUG: Log detailed information about the documensoID field
|
||||
console.log('[EOISection] DEBUGGING hasGeneratedEOI computation:', {
|
||||
interestId: props.interest.Id,
|
||||
documensoID: props.interest['documensoID'],
|
||||
documensoID_type: typeof props.interest['documensoID'],
|
||||
documensoID_raw: JSON.stringify(props.interest['documensoID']),
|
||||
documensoID_exists: 'documensoID' in props.interest,
|
||||
documensoID_truthy: !!props.interest['documensoID'],
|
||||
signature_links: {
|
||||
client: props.interest['Signature Link Client'],
|
||||
cc: props.interest['Signature Link CC'],
|
||||
developer: props.interest['Signature Link Developer']
|
||||
},
|
||||
all_interest_keys: Object.keys(props.interest).filter(key => key.toLowerCase().includes('documen')),
|
||||
result: !!props.interest['documensoID']
|
||||
});
|
||||
|
||||
return !!(props.interest['documensoID']);
|
||||
});
|
||||
|
||||
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 debugEmbeddedUrls = computed(() => {
|
||||
return {
|
||||
client: {
|
||||
embedded: props.interest['EmbeddedSignatureLinkClient'],
|
||||
original: props.interest['Signature Link Client'],
|
||||
final: getClientSignatureUrl.value
|
||||
},
|
||||
cc: {
|
||||
embedded: props.interest['EmbeddedSignatureLinkCC'],
|
||||
original: props.interest['Signature Link CC'],
|
||||
final: getCCSignatureUrl.value
|
||||
},
|
||||
developer: {
|
||||
embedded: props.interest['EmbeddedSignatureLinkDeveloper'],
|
||||
original: props.interest['Signature Link Developer'],
|
||||
final: getDeveloperSignatureUrl.value
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const generateEOI = async (retryCount = 0) => {
|
||||
isGenerating.value = true;
|
||||
|
||||
try {
|
||||
const response = await $fetch<{
|
||||
success: boolean;
|
||||
documentId: string | number;
|
||||
clientSigningUrl: string;
|
||||
signingLinks: Record<string, string>;
|
||||
}>('/api/email/generate-eoi-document', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-tag': '094ut234'
|
||||
},
|
||||
body: {
|
||||
interestId: props.interest.Id.toString()
|
||||
}
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
toast.success(response.documentId === 'existing'
|
||||
? 'EOI already exists - signature links retrieved'
|
||||
: 'EOI generated successfully');
|
||||
|
||||
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);
|
||||
|
||||
// Retry logic
|
||||
if (retryCount < 3) {
|
||||
console.log(`Retrying EOI generation... Attempt ${retryCount + 2}/4`);
|
||||
await new Promise(resolve => setTimeout(resolve, (retryCount + 1) * 1000));
|
||||
return generateEOI(retryCount + 1);
|
||||
}
|
||||
|
||||
// Show error message after all retries failed
|
||||
toast.error(error.data?.statusMessage || error.message || 'Failed to generate EOI after multiple attempts');
|
||||
} finally {
|
||||
isGenerating.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const checkSignatureStatus = async () => {
|
||||
if (!props.interest['documensoID']) return;
|
||||
|
||||
isCheckingSignatures.value = true;
|
||||
|
||||
try {
|
||||
const response = await $fetch<any>('/api/eoi/check-signature-status', {
|
||||
headers: {
|
||||
'x-tag': '094ut234'
|
||||
},
|
||||
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`);
|
||||
|
||||
// Log debug info
|
||||
console.log('[EOISection] Link copied:', {
|
||||
type: linkType,
|
||||
isEmbedded,
|
||||
url: link
|
||||
});
|
||||
|
||||
// Update EOI Time Sent if not already set
|
||||
if (!props.interest['EOI Time Sent']) {
|
||||
try {
|
||||
await $fetch('/api/update-interest', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'x-tag': '094ut234'
|
||||
},
|
||||
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',
|
||||
headers: {
|
||||
'x-tag': '094ut234'
|
||||
},
|
||||
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 || 'Failed to upload EOI document');
|
||||
} finally {
|
||||
isUploading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpload = () => {
|
||||
if (selectedFile.value) {
|
||||
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',
|
||||
headers: {
|
||||
'x-tag': '094ut234'
|
||||
},
|
||||
body: {
|
||||
interestId: props.interest.Id.toString()
|
||||
}
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
toast.success('Uploaded EOI deleted successfully');
|
||||
showDeleteConfirmDialog.value = false;
|
||||
emit('update'); // Refresh parent data
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Failed to delete uploaded EOI:', error);
|
||||
toast.error(error.data?.statusMessage || 'Failed to delete uploaded EOI');
|
||||
} finally {
|
||||
isDeleting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
>>>>>>> 219522e1b532fdb5f8e445b3dc250d6fe23f90f6
|
||||
const deleteGeneratedEOI = async () => {
|
||||
// Prevent multiple simultaneous deletion attempts
|
||||
if (isDeletingGenerated.value) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue