diff --git a/components/EmailComposer.vue b/components/EmailComposer.vue
index 4cd96cc..3974d5e 100644
--- a/components/EmailComposer.vue
+++ b/components/EmailComposer.vue
@@ -171,25 +171,40 @@
-
+
-
- Select Files to Attach
+
+
+ mdi-attachment
+ Select Files to Attach
+
mdi-close
-
-
-
+
+
+
+
+
+
-
+
+
+
+
- Cancel
+
+ Cancel
+
@@ -220,6 +235,7 @@ const emit = defineEmits();
const user = useDirectusUser();
const toast = useToast();
+const { mobile } = useDisplay();
const form = ref();
const sending = ref(false);
diff --git a/components/InterestDetailsModal.vue b/components/InterestDetailsModal.vue
index 2e81edc..a5bd9cf 100644
--- a/components/InterestDetailsModal.vue
+++ b/components/InterestDetailsModal.vue
@@ -66,7 +66,7 @@
variant="flat"
color="success"
size="large"
- @click="saveInterest"
+ @click="() => debouncedSaveInterest ? debouncedSaveInterest() : saveInterest()"
:loading="isSaving"
:disabled="isSaving || isDeleting"
class="ml-2"
@@ -195,7 +195,7 @@
debouncedSaveInterest ? debouncedSaveInterest() : saveInterest()"
variant="flat"
color="success"
block
@@ -258,15 +258,6 @@
prepend-inner-icon="mdi-map-marker"
>
-
-
-
+
+
+
+
+ {{ item.raw.letter }}
+
+
+
+
+
+
+
+
+
+ {{ item.raw.letter }}
+
+
+
+
+
([]);
const originalBerths = ref([]);
const originalBerthRecommendations = ref([]);
-// Auto-save function (debounced)
-const autoSave = debounce(async () => {
- if (!hasUnsavedChanges.value || !interest.value) return;
-
- console.log('Auto-saving interest...');
- await saveInterest(true); // Pass true to indicate auto-save
-}, 2000); // 2 second delay
+// Store the debounced functions
+let autoSave: any = null;
+let debouncedSaveInterest: any = null;
+let debouncedDeleteInterest: any = null;
+
+// Initialize debounced functions
+const initializeDebouncedFunctions = () => {
+ // Auto-save function (debounced)
+ autoSave = debounce(async () => {
+ if (!hasUnsavedChanges.value || !interest.value || isSaving.value) return;
+
+ console.log('Auto-saving interest...');
+ await saveInterest(true); // Pass true to indicate auto-save
+ }, 2000); // 2 second delay
+
+ // Debounced manual save
+ debouncedSaveInterest = debounce(async () => {
+ await saveInterest();
+ }, 300); // 300ms delay to prevent multiple clicks
+
+ // Debounced delete
+ debouncedDeleteInterest = debounce(async () => {
+ await deleteInterest();
+ }, 300); // 300ms delay to prevent multiple clicks
+};
+
+// Initialize on component creation
+initializeDebouncedFunctions();
// Watch for changes to trigger auto-save
watch(
@@ -709,7 +749,37 @@ watch(
(newValue, oldValue) => {
if (newValue && oldValue && JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
hasUnsavedChanges.value = true;
- autoSave();
+ // Cancel any pending saves
+ if (debouncedSaveInterest) debouncedSaveInterest.cancel();
+ if (autoSave) autoSave();
+ }
+ },
+ { deep: true }
+);
+
+// Watch yacht information and berth size to auto-set Sales Process Level
+watch(
+ () => {
+ if (!interest.value) return null;
+ return {
+ yachtName: interest.value['Yacht Name'],
+ length: interest.value.Length,
+ width: interest.value.Width,
+ depth: interest.value.Depth,
+ berthSize: interest.value['Berth Size Desired']
+ };
+ },
+ (newValues) => {
+ if (!newValues || !interest.value) return;
+
+ // Check if any yacht information is provided or berth size is provided
+ const hasYachtInfo = !!(newValues.yachtName || newValues.length || newValues.width || newValues.depth);
+ const hasBerthSize = !!newValues.berthSize;
+
+ if ((hasYachtInfo || hasBerthSize) && interest.value['Sales Process Level'] === 'General Qualified Interest') {
+ // Auto-set to Specific Qualified Interest
+ interest.value['Sales Process Level'] = 'Specific Qualified Interest';
+ console.log('Auto-setting Sales Process Level to Specific Qualified Interest');
}
},
{ deep: true }
@@ -761,7 +831,11 @@ const closeModal = () => {
};
const handleFormSubmit = () => {
- saveInterest();
+ if (debouncedSaveInterest) {
+ debouncedSaveInterest();
+ } else {
+ saveInterest();
+ }
};
const saveInterest = async (isAutoSave = false) => {
@@ -884,6 +958,34 @@ const eoiSendToSales = async () => {
}
};
+// Group berths by first letter
+const groupedBerths = computed(() => {
+ const grouped: Record = {};
+ const sortedBerths = [...availableBerths.value].sort((a, b) =>
+ (a['Mooring Number'] || '').localeCompare(b['Mooring Number'] || '')
+ );
+
+ sortedBerths.forEach(berth => {
+ const firstLetter = (berth['Mooring Number'] || '').charAt(0).toUpperCase();
+ if (!grouped[firstLetter]) {
+ grouped[firstLetter] = [];
+ }
+ grouped[firstLetter].push(berth);
+ });
+
+ // Create flat array with dividers
+ const result: any[] = [];
+ Object.keys(grouped).sort().forEach((letter, index) => {
+ if (index > 0) {
+ // Add divider
+ result.push({ isDivider: true, letter });
+ }
+ result.push(...grouped[letter]);
+ });
+
+ return result;
+});
+
// Load all available berths
const loadAvailableBerths = async () => {
loadingBerths.value = true;
@@ -1120,7 +1222,11 @@ 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();
+ if (debouncedDeleteInterest) {
+ debouncedDeleteInterest();
+ } else {
+ deleteInterest();
+ }
}
};
diff --git a/server/api/delete-interest.ts b/server/api/delete-interest.ts
index 2b54124..4b33361 100644
--- a/server/api/delete-interest.ts
+++ b/server/api/delete-interest.ts
@@ -1,34 +1,68 @@
-import { deleteInterest } from '~/server/utils/nocodb';
+import { deleteInterest, getInterestById } from '~/server/utils/nocodb';
export default defineEventHandler(async (event) => {
+ const startTime = Date.now();
const xTagHeader = getRequestHeader(event, "x-tag");
- console.log('[delete-interest] Request received with x-tag:', xTagHeader);
+ console.log('[delete-interest] =========================');
+ console.log('[delete-interest] Request received at:', new Date().toISOString());
+ console.log('[delete-interest] x-tag:', xTagHeader);
if (!xTagHeader || (xTagHeader !== "094ut234" && xTagHeader !== "pjnvü1230")) {
console.error('[delete-interest] Authentication failed - invalid x-tag:', xTagHeader);
+ console.log('[delete-interest] Duration:', Date.now() - startTime, 'ms');
throw createError({ statusCode: 401, statusMessage: "unauthenticated" });
}
try {
const body = await readBody(event);
const { id } = body;
- console.log('[delete-interest] Request body:', { id });
+ console.log('[delete-interest] Request body:', JSON.stringify(body, null, 2));
if (!id) {
console.error('[delete-interest] Missing ID in request');
+ console.log('[delete-interest] Duration:', Date.now() - startTime, 'ms');
throw createError({ statusCode: 400, statusMessage: "ID is required" });
}
- console.log('[delete-interest] Deleting interest:', id);
+ // Pre-delete verification
+ console.log('[delete-interest] Pre-delete verification - checking if record exists...');
+ try {
+ const existingRecord = await getInterestById(id);
+ console.log('[delete-interest] Record found:', {
+ id: existingRecord.Id,
+ name: existingRecord['Full Name'],
+ email: existingRecord['Email Address']
+ });
+ } catch (verifyError: any) {
+ console.error('[delete-interest] Pre-delete verification failed:', verifyError);
+ if (verifyError.statusCode === 404 || verifyError.status === 404) {
+ console.error('[delete-interest] Record does not exist - cannot delete');
+ throw createError({ statusCode: 404, statusMessage: "Record not found" });
+ }
+ }
+
+ console.log('[delete-interest] Proceeding with deletion for ID:', id);
const result = await deleteInterest(id);
- console.log('[delete-interest] Successfully deleted interest:', id);
+ console.log('[delete-interest] Delete operation completed successfully');
+ console.log('[delete-interest] Result:', JSON.stringify(result, null, 2));
+ console.log('[delete-interest] Duration:', Date.now() - startTime, 'ms');
+ console.log('[delete-interest] =========================');
return result;
- } catch (error) {
- console.error('[delete-interest] Error occurred:', error);
- console.error('[delete-interest] Error stack:', error instanceof Error ? error.stack : 'No stack trace');
+ } catch (error: any) {
+ console.error('[delete-interest] =========================');
+ console.error('[delete-interest] ERROR OCCURRED');
+ console.error('[delete-interest] Error type:', error.constructor.name);
+ console.error('[delete-interest] Error message:', error.message);
+ console.error('[delete-interest] Error status:', error.statusCode || error.status || 'unknown');
+ console.error('[delete-interest] Error stack:', error.stack || 'No stack trace');
+ console.error('[delete-interest] Full error object:', JSON.stringify(error, null, 2));
+ console.error('[delete-interest] Duration:', Date.now() - startTime, 'ms');
+ console.error('[delete-interest] =========================');
- if (error instanceof Error) {
+ if (error.statusCode || error.status) {
+ throw error; // Re-throw if it's already a proper error
+ } else if (error instanceof Error) {
throw createError({ statusCode: 500, statusMessage: error.message });
} else {
throw createError({
diff --git a/server/utils/nocodb.ts b/server/utils/nocodb.ts
index d8f2f2a..b95fa02 100644
--- a/server/utils/nocodb.ts
+++ b/server/utils/nocodb.ts
@@ -84,7 +84,6 @@ export const updateInterest = async (id: string, data: Partial, retryC
"Depth",
"Created At",
"Source",
- "Place of Residence",
"Contact Method Preferred",
"Request Form Sent",
"Berth Number",
@@ -198,7 +197,6 @@ export const createInterest = async (data: Partial) => {
"Width",
"Depth",
"Source",
- "Place of Residence",
"Contact Method Preferred",
"Lead Category",
"EOI Status",
@@ -243,10 +241,27 @@ export const createInterest = async (data: Partial) => {
};
export const deleteInterest = async (id: string) => {
- console.log('[nocodb.deleteInterest] Deleting interest:', id);
+ const startTime = Date.now();
+ console.log('[nocodb.deleteInterest] =========================');
+ console.log('[nocodb.deleteInterest] DELETE operation started at:', new Date().toISOString());
+ console.log('[nocodb.deleteInterest] Target ID:', id);
+
const url = createTableUrl(Table.Interest);
console.log('[nocodb.deleteInterest] URL:', url);
+ const requestBody = {
+ "Id": parseInt(id)
+ };
+
+ console.log('[nocodb.deleteInterest] Request configuration:');
+ console.log(' Method: DELETE');
+ console.log(' URL:', url);
+ console.log(' Headers:', {
+ "xc-token": getNocoDbConfiguration().token ? "***" + getNocoDbConfiguration().token.slice(-4) : "not set",
+ "Content-Type": "application/json"
+ });
+ console.log(' Body:', JSON.stringify(requestBody, null, 2));
+
try {
// According to NocoDB API docs, DELETE requires ID in the body
const result = await $fetch(url, {
@@ -255,15 +270,26 @@ export const deleteInterest = async (id: string) => {
"xc-token": getNocoDbConfiguration().token,
"Content-Type": "application/json"
},
- body: {
- "Id": parseInt(id)
- }
+ body: requestBody
});
- console.log('[nocodb.deleteInterest] Delete successful for ID:', id);
+
+ console.log('[nocodb.deleteInterest] DELETE successful');
+ console.log('[nocodb.deleteInterest] Response:', JSON.stringify(result, null, 2));
+ console.log('[nocodb.deleteInterest] Duration:', Date.now() - startTime, 'ms');
+ console.log('[nocodb.deleteInterest] =========================');
+
return result;
- } catch (error) {
- console.error('[nocodb.deleteInterest] Delete failed:', error);
- console.error('[nocodb.deleteInterest] Error details:', error instanceof Error ? error.message : 'Unknown error');
+ } catch (error: any) {
+ console.error('[nocodb.deleteInterest] =========================');
+ console.error('[nocodb.deleteInterest] DELETE FAILED');
+ console.error('[nocodb.deleteInterest] Error type:', error.constructor.name);
+ console.error('[nocodb.deleteInterest] Error message:', error.message);
+ console.error('[nocodb.deleteInterest] Error status:', error.statusCode || error.status || 'unknown');
+ console.error('[nocodb.deleteInterest] Error data:', error.data);
+ console.error('[nocodb.deleteInterest] Error stack:', error.stack || 'No stack trace');
+ console.error('[nocodb.deleteInterest] Full error:', JSON.stringify(error, null, 2));
+ console.error('[nocodb.deleteInterest] Duration:', Date.now() - startTime, 'ms');
+ console.error('[nocodb.deleteInterest] =========================');
throw error;
}
};