diff --git a/components/ClientEmailSection.vue b/components/ClientEmailSection.vue index 93428bf..0060c6d 100644 --- a/components/ClientEmailSection.vue +++ b/components/ClientEmailSection.vue @@ -74,9 +74,123 @@ -
-
Email History
- + diff --git a/components/InterestDetailsModal.vue b/components/InterestDetailsModal.vue index ad10717..f7ff93c 100644 --- a/components/InterestDetailsModal.vue +++ b/components/InterestDetailsModal.vue @@ -1222,24 +1222,22 @@ const getSalesLevelColor = (level: string) => { // Confirm delete const confirmDelete = () => { - if (!interest.value) return; + if (!interest.value || isDeleting.value) return; if (confirm(`Are you sure you want to delete the interest for ${interest.value['Full Name']}? This action cannot be undone.`)) { - if (debouncedDeleteInterest) { - debouncedDeleteInterest(); - } else { - deleteInterest(); - } + deleteInterest(); } }; // Delete interest const deleteInterest = async () => { - if (!interest.value) return; + if (!interest.value || isDeleting.value) return; + console.log('[InterestDetailsModal] Starting delete for interest:', interest.value.Id); isDeleting.value = true; + try { - await $fetch("/api/delete-interest", { + const response = await $fetch("/api/delete-interest", { method: "POST", headers: { "x-tag": user.value?.email ? "094ut234" : "pjnvü1230", @@ -1249,12 +1247,15 @@ const deleteInterest = async () => { }, }); + console.log('[InterestDetailsModal] Delete response:', response); 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."); + } catch (error: any) { + console.error("[InterestDetailsModal] Failed to delete interest:", error); + console.error("[InterestDetailsModal] Error details:", error.data || error.message); + const errorMessage = error.data?.statusMessage || error.message || "Failed to delete interest. Please try again."; + toast.error(errorMessage); } finally { isDeleting.value = false; } diff --git a/pages/dashboard/interest-list.vue b/pages/dashboard/interest-list.vue index 9454858..3db253f 100644 --- a/pages/dashboard/interest-list.vue +++ b/pages/dashboard/interest-list.vue @@ -585,37 +585,51 @@ const getRelativeTime = (dateString: string) => { /* Mobile-specific styles */ @media (max-width: 768px) { .table-container { - margin: 0 -12px; - padding: 0 12px; + position: relative; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + margin: 0 -16px; } - /* For mobile, only show essential columns */ + /* Add padding to the wrapper instead */ .modern-table :deep(.v-table__wrapper) { - min-width: auto; + padding: 0 16px; + min-width: 600px; /* Minimum width to ensure scrolling */ } .modern-table :deep(th) { padding: 8px !important; font-size: 0.75rem; + white-space: nowrap; } .modern-table :deep(td) { padding: 12px 8px !important; } - /* Hide columns on mobile that aren't in mobile headers */ - .modern-table :deep(th:nth-child(n+4)), - .modern-table :deep(td:nth-child(n+4)) { - display: none; + /* Show all columns but with smaller widths on mobile */ + .modern-table :deep(th:nth-child(1)), + .modern-table :deep(td:nth-child(1)) { + min-width: 180px !important; + } + + .modern-table :deep(th:nth-child(2)), + .modern-table :deep(td:nth-child(2)) { + min-width: 120px !important; + } + + .modern-table :deep(th:nth-child(3)), + .modern-table :deep(td:nth-child(3)) { + min-width: 100px !important; } /* Contact cell optimization */ .contact-cell { - max-width: 200px; + max-width: 180px; } .contact-cell .text-truncate { - max-width: 150px; + max-width: 140px; } /* Adjust table row height on mobile */ @@ -628,6 +642,18 @@ const getRelativeTime = (dateString: string) => { height: 20px !important; font-size: 0.625rem !important; } + + /* Add visual scroll indicators */ + .table-container::after { + content: ''; + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 30px; + background: linear-gradient(to right, transparent, rgba(255,255,255,0.8)); + pointer-events: none; + } } /* Ensure proper text truncation */ diff --git a/server/api/eoi/upload-document.ts b/server/api/eoi/upload-document.ts index 153e668..b1c4195 100644 --- a/server/api/eoi/upload-document.ts +++ b/server/api/eoi/upload-document.ts @@ -107,7 +107,16 @@ export default defineEventHandler(async (event) => { // Update interest with EOI document information console.log('[EOI Upload] Updating interest with EOI document info'); - await updateInterestEOIDocument(interestId, documentData); + console.log('[EOI Upload] Document data:', JSON.stringify(documentData, null, 2)); + + try { + await updateInterestEOIDocument(interestId, documentData); + console.log('[EOI Upload] Successfully updated EOI document in database'); + } catch (dbError: any) { + console.error('[EOI Upload] Failed to update database with EOI document:', dbError); + console.error('[EOI Upload] Database error details:', dbError.data || dbError.message); + throw new Error(`Failed to update database: ${dbError.message}`); + } // Update the status fields for uploaded (signed) EOI const updateData: any = { @@ -116,18 +125,26 @@ export default defineEventHandler(async (event) => { 'Sales Process Level': 'Signed LOI and NDA' }; console.log('[EOI Upload] Updating interest status fields for signed EOI'); + console.log('[EOI Upload] Status update data:', JSON.stringify(updateData, null, 2)); - // Update the interest - await $fetch('/api/update-interest', { - method: 'POST', - headers: { - 'x-tag': xTagHeader, - }, - body: { - id: interestId, - data: updateData - } - }); + try { + // Update the interest + await $fetch('/api/update-interest', { + method: 'POST', + headers: { + 'x-tag': xTagHeader, + }, + body: { + id: interestId, + data: updateData + } + }); + console.log('[EOI Upload] Successfully updated interest status'); + } catch (statusError: any) { + console.error('[EOI Upload] Failed to update interest status:', statusError); + console.error('[EOI Upload] Status error details:', statusError.data || statusError.message); + // Don't throw here - the file was uploaded successfully + } console.log('[EOI Upload] Upload completed successfully'); return { diff --git a/server/api/files/upload.ts b/server/api/files/upload.ts index 2784a09..dd46d55 100644 --- a/server/api/files/upload.ts +++ b/server/api/files/upload.ts @@ -4,12 +4,20 @@ import { promises as fs } from 'fs'; import mime from 'mime-types'; export default defineEventHandler(async (event) => { + const xTagHeader = getRequestHeader(event, "x-tag"); + + if (!xTagHeader || (xTagHeader !== "094ut234" && xTagHeader !== "pjnvü1230")) { + throw createError({ statusCode: 401, statusMessage: "unauthenticated" }); + } + try { // Get the current path and bucket from query params const query = getQuery(event); const currentPath = (query.path as string) || ''; const bucket = (query.bucket as string) || 'client-portal'; // Default bucket + console.log('[Upload] Request received for bucket:', bucket, 'path:', currentPath); + // Parse multipart form data const form = formidable({ maxFileSize: 50 * 1024 * 1024, // 50MB limit @@ -50,6 +58,15 @@ export default defineEventHandler(async (event) => { } else { // For other buckets, use the MinIO client directly const client = getMinioClient(); + + // Ensure bucket exists + try { + await client.bucketExists(bucket); + } catch (err) { + console.log(`[Upload] Bucket ${bucket} doesn't exist, creating it...`); + await client.makeBucket(bucket, 'us-east-1'); + } + await client.putObject(bucket, fullPath, fileBuffer, fileBuffer.length, { 'Content-Type': contentType, });