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
This commit is contained in:
parent
6ebe96bbf4
commit
242e33f7b9
|
|
@ -75,6 +75,18 @@
|
||||||
<v-icon start>mdi-content-save</v-icon>
|
<v-icon start>mdi-content-save</v-icon>
|
||||||
Save Changes
|
Save Changes
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
variant="outlined"
|
||||||
|
color="success"
|
||||||
|
size="large"
|
||||||
|
@click="() => saveInterest(false, true)"
|
||||||
|
:loading="isSaving"
|
||||||
|
:disabled="isSaving || isDeleting"
|
||||||
|
class="ml-2"
|
||||||
|
>
|
||||||
|
<v-icon start>mdi-content-save-move</v-icon>
|
||||||
|
Save & Close
|
||||||
|
</v-btn>
|
||||||
</v-toolbar-items>
|
</v-toolbar-items>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
|
|
||||||
|
|
@ -848,7 +860,7 @@ const handleFormSubmit = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveInterest = async (isAutoSave = false) => {
|
const saveInterest = async (isAutoSave = false, closeAfterSave = false) => {
|
||||||
if (interest.value) {
|
if (interest.value) {
|
||||||
isSaving.value = true;
|
isSaving.value = true;
|
||||||
try {
|
try {
|
||||||
|
|
@ -871,7 +883,11 @@ const saveInterest = async (isAutoSave = false) => {
|
||||||
if (!isAutoSave) {
|
if (!isAutoSave) {
|
||||||
toast.success("Interest saved successfully!");
|
toast.success("Interest saved successfully!");
|
||||||
emit("save", interest.value);
|
emit("save", interest.value);
|
||||||
closeModal();
|
|
||||||
|
// Only close if explicitly requested
|
||||||
|
if (closeAfterSave) {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// For auto-save, just emit save to refresh parent
|
// For auto-save, just emit save to refresh parent
|
||||||
emit("save", interest.value);
|
emit("save", interest.value);
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-divider />
|
<v-divider />
|
||||||
<v-card-text class="pa-4" style="max-height: 600px; overflow-y: auto;">
|
<v-card-text class="pa-4" style="max-height: 600px; overflow-y: auto;">
|
||||||
<div class="d-flex flex-column gap-4">
|
<div class="d-flex flex-column gap-6">
|
||||||
<v-card
|
<v-card
|
||||||
v-for="berth in getBerthsByStatus(status.value)"
|
v-for="berth in getBerthsByStatus(status.value)"
|
||||||
:key="berth.Id"
|
:key="berth.Id"
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ export default defineEventHandler(async (event) => {
|
||||||
await requireAuth(event);
|
await requireAuth(event);
|
||||||
|
|
||||||
console.log('[Delete Generated EOI] Request received');
|
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 {
|
try {
|
||||||
const body = await readBody(event);
|
const body = await readBody(event);
|
||||||
|
|
@ -15,12 +17,13 @@ export default defineEventHandler(async (event) => {
|
||||||
const query = getQuery(event);
|
const query = getQuery(event);
|
||||||
|
|
||||||
console.log('[Delete Generated EOI] Interest ID:', interestId);
|
console.log('[Delete Generated EOI] Interest ID:', interestId);
|
||||||
|
console.log('[Delete Generated EOI] Query params:', query);
|
||||||
|
|
||||||
if (!interestId) {
|
if (!interestId) {
|
||||||
console.error('[Delete Generated EOI] No interest ID provided');
|
console.error('[Delete Generated EOI] No interest ID provided');
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
statusMessage: 'Interest ID is required',
|
statusMessage: 'Interest ID is required. Please provide a valid interest ID.',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,43 @@ import { getExpenses, getCurrentMonthExpenses } from '@/server/utils/nocodb';
|
||||||
import { processExpenseWithCurrency } from '@/server/utils/currency';
|
import { processExpenseWithCurrency } from '@/server/utils/currency';
|
||||||
import type { ExpenseFilters } from '@/utils/types';
|
import type { ExpenseFilters } from '@/utils/types';
|
||||||
|
|
||||||
|
// Retry operation wrapper for database calls
|
||||||
|
async function retryOperation<T>(
|
||||||
|
operation: () => Promise<T>,
|
||||||
|
maxRetries: number = 3,
|
||||||
|
baseDelay: number = 1000
|
||||||
|
): Promise<T> {
|
||||||
|
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) => {
|
export default defineEventHandler(async (event) => {
|
||||||
console.log('[get-expenses] API called with query:', getQuery(event));
|
console.log('[get-expenses] API called with query:', getQuery(event));
|
||||||
|
|
||||||
|
|
@ -10,6 +47,7 @@ export default defineEventHandler(async (event) => {
|
||||||
// Set proper headers
|
// Set proper headers
|
||||||
setHeader(event, 'Cache-Control', 'no-cache');
|
setHeader(event, 'Cache-Control', 'no-cache');
|
||||||
setHeader(event, 'Content-Type', 'application/json');
|
setHeader(event, 'Content-Type', 'application/json');
|
||||||
|
|
||||||
// Check authentication first
|
// Check authentication first
|
||||||
try {
|
try {
|
||||||
await requireSalesOrAdmin(event);
|
await requireSalesOrAdmin(event);
|
||||||
|
|
@ -36,7 +74,7 @@ export default defineEventHandler(async (event) => {
|
||||||
console.log('[get-expenses] No date filters provided, defaulting to current month');
|
console.log('[get-expenses] No date filters provided, defaulting to current month');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await getCurrentMonthExpenses();
|
const result = await retryOperation(() => getCurrentMonthExpenses());
|
||||||
|
|
||||||
// Process expenses with currency conversion
|
// Process expenses with currency conversion
|
||||||
const processedExpenses = await Promise.all(
|
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({
|
throw createError({
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
statusMessage: 'Unable to fetch expense data. Please try again later.'
|
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);
|
console.log('[get-expenses] Fetching expenses with filters:', filters);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await getExpenses(filters);
|
const result = await retryOperation(() => getExpenses(filters));
|
||||||
|
|
||||||
// Process expenses with currency conversion
|
// Process expenses with currency conversion
|
||||||
const processedExpenses = await Promise.all(
|
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({
|
throw createError({
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
statusMessage: 'Unable to fetch expense data. Please try again later.'
|
statusMessage: 'Unable to fetch expense data. Please try again later.'
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,12 @@ export default defineEventHandler(async (event) => {
|
||||||
|
|
||||||
let url = `${config.url}/api/v2/tables/${interestTableId}/records`;
|
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) {
|
if (dateRange && dateRange > 0) {
|
||||||
const cutoffDate = new Date();
|
const cutoffDate = new Date();
|
||||||
cutoffDate.setDate(cutoffDate.getDate() - dateRange);
|
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)}`;
|
url += `?where=${encodeURIComponent(dateFilter)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue