From 242e33f7b92aa851c1f3b181962eaa72ee0bdfe3 Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 10 Jul 2025 17:36:10 -0400 Subject: [PATCH] feat: Add "Save & Close" button in InterestDetailsModal, enhance saveInterest function to conditionally close modal, and improve logging in delete-generated-document and get-expenses APIs --- components/InterestDetailsModal.vue | 20 +++++++- pages/dashboard/interest-berth-status.vue | 2 +- server/api/eoi/delete-generated-document.ts | 5 +- server/api/get-expenses.ts | 56 ++++++++++++++++++++- server/api/interests/duplicates/find.ts | 5 +- 5 files changed, 80 insertions(+), 8 deletions(-) diff --git a/components/InterestDetailsModal.vue b/components/InterestDetailsModal.vue index e9b6c2e..3adea59 100644 --- a/components/InterestDetailsModal.vue +++ b/components/InterestDetailsModal.vue @@ -75,6 +75,18 @@ mdi-content-save Save Changes + + mdi-content-save-move + Save & Close + @@ -848,7 +860,7 @@ const handleFormSubmit = () => { } }; -const saveInterest = async (isAutoSave = false) => { +const saveInterest = async (isAutoSave = false, closeAfterSave = false) => { if (interest.value) { isSaving.value = true; try { @@ -871,7 +883,11 @@ const saveInterest = async (isAutoSave = false) => { if (!isAutoSave) { toast.success("Interest saved successfully!"); emit("save", interest.value); - closeModal(); + + // Only close if explicitly requested + if (closeAfterSave) { + closeModal(); + } } else { // For auto-save, just emit save to refresh parent emit("save", interest.value); diff --git a/pages/dashboard/interest-berth-status.vue b/pages/dashboard/interest-berth-status.vue index 6629153..24a809c 100644 --- a/pages/dashboard/interest-berth-status.vue +++ b/pages/dashboard/interest-berth-status.vue @@ -116,7 +116,7 @@ -
+
{ await requireAuth(event); console.log('[Delete Generated EOI] Request received'); + console.log('[Delete Generated EOI] Request headers:', getHeaders(event)); + console.log('[Delete Generated EOI] Request method:', getMethod(event)); try { const body = await readBody(event); @@ -15,12 +17,13 @@ export default defineEventHandler(async (event) => { const query = getQuery(event); console.log('[Delete Generated EOI] Interest ID:', interestId); + console.log('[Delete Generated EOI] Query params:', query); if (!interestId) { console.error('[Delete Generated EOI] No interest ID provided'); throw createError({ statusCode: 400, - statusMessage: 'Interest ID is required', + statusMessage: 'Interest ID is required. Please provide a valid interest ID.', }); } diff --git a/server/api/get-expenses.ts b/server/api/get-expenses.ts index 44ce6a6..4d41b27 100644 --- a/server/api/get-expenses.ts +++ b/server/api/get-expenses.ts @@ -3,6 +3,43 @@ import { getExpenses, getCurrentMonthExpenses } from '@/server/utils/nocodb'; import { processExpenseWithCurrency } from '@/server/utils/currency'; import type { ExpenseFilters } from '@/utils/types'; +// Retry operation wrapper for database calls +async function retryOperation( + operation: () => Promise, + maxRetries: number = 3, + baseDelay: number = 1000 +): Promise { + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + return await operation(); + } catch (error: any) { + console.log(`[get-expenses] Attempt ${attempt}/${maxRetries} failed:`, error.message); + + // Don't retry on authentication/authorization errors + if (error.statusCode === 401 || error.statusCode === 403) { + throw error; + } + + // Don't retry on client errors (4xx except 404) + if (error.statusCode >= 400 && error.statusCode < 500 && error.statusCode !== 404) { + throw error; + } + + // If this is the last attempt, throw the error + if (attempt === maxRetries) { + throw error; + } + + // For retryable errors (5xx, network errors, timeouts), wait before retry + const delay = baseDelay * Math.pow(2, attempt - 1); // Exponential backoff + console.log(`[get-expenses] Retrying in ${delay}ms...`); + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + + throw new Error('Retry operation failed unexpectedly'); +} + export default defineEventHandler(async (event) => { console.log('[get-expenses] API called with query:', getQuery(event)); @@ -10,6 +47,7 @@ export default defineEventHandler(async (event) => { // Set proper headers setHeader(event, 'Cache-Control', 'no-cache'); setHeader(event, 'Content-Type', 'application/json'); + // Check authentication first try { await requireSalesOrAdmin(event); @@ -36,7 +74,7 @@ export default defineEventHandler(async (event) => { console.log('[get-expenses] No date filters provided, defaulting to current month'); try { - const result = await getCurrentMonthExpenses(); + const result = await retryOperation(() => getCurrentMonthExpenses()); // Process expenses with currency conversion const processedExpenses = await Promise.all( @@ -57,6 +95,13 @@ export default defineEventHandler(async (event) => { }); } + if (dbError.statusCode === 404) { + throw createError({ + statusCode: 404, + statusMessage: 'No expense records found for the current month.' + }); + } + throw createError({ statusCode: 500, statusMessage: 'Unable to fetch expense data. Please try again later.' @@ -86,7 +131,7 @@ export default defineEventHandler(async (event) => { console.log('[get-expenses] Fetching expenses with filters:', filters); try { - const result = await getExpenses(filters); + const result = await retryOperation(() => getExpenses(filters)); // Process expenses with currency conversion const processedExpenses = await Promise.all( @@ -126,6 +171,13 @@ export default defineEventHandler(async (event) => { }); } + if (dbError.statusCode === 404) { + throw createError({ + statusCode: 404, + statusMessage: 'No expense records found matching the specified criteria.' + }); + } + throw createError({ statusCode: 500, statusMessage: 'Unable to fetch expense data. Please try again later.' diff --git a/server/api/interests/duplicates/find.ts b/server/api/interests/duplicates/find.ts index d1ca86d..835f440 100644 --- a/server/api/interests/duplicates/find.ts +++ b/server/api/interests/duplicates/find.ts @@ -19,11 +19,12 @@ export default defineEventHandler(async (event) => { let url = `${config.url}/api/v2/tables/${interestTableId}/records`; - // Add date filtering if specified + // Add date filtering if specified (include records without Created At) if (dateRange && dateRange > 0) { const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - dateRange); - const dateFilter = `(Created At,gte,${cutoffDate.toISOString()})`; + // Include records without Created At OR within date range + const dateFilter = `((Created At,gte,${cutoffDate.toISOString()}),or,(Created At,is,null))`; url += `?where=${encodeURIComponent(dateFilter)}`; }