This commit is contained in:
Matt 2025-06-10 20:45:47 +02:00
parent e5b8affa84
commit cf4af2cbff
5 changed files with 82 additions and 68 deletions

View File

@ -94,12 +94,17 @@
<v-list-item-title :class="mobile ? 'text-body-2' : ''">Client Signature Link</v-list-item-title> <v-list-item-title :class="mobile ? 'text-body-2' : ''">Client Signature Link</v-list-item-title>
<v-list-item-subtitle :class="mobile ? 'text-caption' : ''">{{ interest['Full Name'] }}</v-list-item-subtitle> <v-list-item-subtitle :class="mobile ? 'text-caption' : ''">{{ interest['Full Name'] }}</v-list-item-subtitle>
<template v-slot:append> <template v-slot:append>
<v-btn <v-btn-group variant="text" :size="mobile ? 'small' : 'default'">
icon="mdi-content-copy" <v-btn
variant="text" icon="mdi-content-copy"
:size="mobile ? 'small' : 'default'" @click="copyLink(interest['Signature Link Client'])"
@click="copyLink(interest['Signature Link Client'])" ></v-btn>
></v-btn> <v-btn
icon="mdi-open-in-new"
:href="interest['Signature Link Client']"
target="_blank"
></v-btn>
</v-btn-group>
</template> </template>
</v-list-item> </v-list-item>
@ -112,12 +117,17 @@
<v-list-item-title :class="mobile ? 'text-body-2' : ''">CC Signature Link</v-list-item-title> <v-list-item-title :class="mobile ? 'text-body-2' : ''">CC Signature Link</v-list-item-title>
<v-list-item-subtitle :class="mobile ? 'text-caption' : ''">Oscar Faragher</v-list-item-subtitle> <v-list-item-subtitle :class="mobile ? 'text-caption' : ''">Oscar Faragher</v-list-item-subtitle>
<template v-slot:append> <template v-slot:append>
<v-btn <v-btn-group variant="text" :size="mobile ? 'small' : 'default'">
icon="mdi-content-copy" <v-btn
variant="text" icon="mdi-content-copy"
:size="mobile ? 'small' : 'default'" @click="copyLink(interest['Signature Link CC'])"
@click="copyLink(interest['Signature Link CC'])" ></v-btn>
></v-btn> <v-btn
icon="mdi-open-in-new"
:href="interest['Signature Link CC']"
target="_blank"
></v-btn>
</v-btn-group>
</template> </template>
</v-list-item> </v-list-item>
@ -130,12 +140,17 @@
<v-list-item-title :class="mobile ? 'text-body-2' : ''">Developer Signature Link</v-list-item-title> <v-list-item-title :class="mobile ? 'text-body-2' : ''">Developer Signature Link</v-list-item-title>
<v-list-item-subtitle :class="mobile ? 'text-caption' : ''">David Mizrahi</v-list-item-subtitle> <v-list-item-subtitle :class="mobile ? 'text-caption' : ''">David Mizrahi</v-list-item-subtitle>
<template v-slot:append> <template v-slot:append>
<v-btn <v-btn-group variant="text" :size="mobile ? 'small' : 'default'">
icon="mdi-content-copy" <v-btn
variant="text" icon="mdi-content-copy"
:size="mobile ? 'small' : 'default'" @click="copyLink(interest['Signature Link Developer'])"
@click="copyLink(interest['Signature Link Developer'])" ></v-btn>
></v-btn> <v-btn
icon="mdi-open-in-new"
:href="interest['Signature Link Developer']"
target="_blank"
></v-btn>
</v-btn-group>
</template> </template>
</v-list-item> </v-list-item>
</v-list> </v-list>

View File

@ -139,14 +139,25 @@ const getAttachmentName = (attachment: any) => {
}; };
const getAttachmentUrl = (attachment: any) => { const getAttachmentUrl = (attachment: any) => {
// If attachment has a path and bucket, construct the download URL // If attachment is just a string (filename), assume it's in the client-emails bucket
if (attachment.path && attachment.bucket) { if (typeof attachment === 'string') {
return `/api/files/proxy-download?path=${encodeURIComponent(attachment.path)}&bucket=${attachment.bucket}`; return `/api/files/proxy-download?fileName=${encodeURIComponent(attachment)}&bucket=client-emails`;
} }
// If it's just a URL, return it
if (attachment.url) return attachment.url; // If it has a path property, use that
// Otherwise return a placeholder if (attachment?.path) {
return '#'; const bucket = attachment.bucket || 'client-emails';
return `/api/files/proxy-download?fileName=${encodeURIComponent(attachment.path)}&bucket=${bucket}`;
}
// If it has a url property, use that directly
if (attachment?.url) {
return attachment.url;
}
// Default fallback
const name = attachment?.name || attachment?.filename || 'attachment';
return `/api/files/proxy-download?fileName=${encodeURIComponent(name)}&bucket=client-emails`;
}; };
</script> </script>

View File

@ -196,7 +196,7 @@
</v-col> </v-col>
<v-col cols="12"> <v-col cols="12">
<v-btn <v-btn
@click="() => debouncedSaveInterest ? debouncedSaveInterest() : saveInterest()" @click="handleMobileSave"
variant="flat" variant="flat"
color="success" color="success"
block block
@ -407,46 +407,8 @@
prepend-inner-icon="mdi-tag" prepend-inner-icon="mdi-tag"
></v-select> ></v-select>
</v-col> </v-col>
<v-col cols="12" md="6"> <!-- Berth Recommendations field hidden per user request -->
<v-autocomplete <v-col cols="12" md="12">
v-model="selectedBerthRecommendations"
:items="groupedBerths"
:item-title="(item) => item.isDivider ? '' : item['Mooring Number']"
:item-value="(item) => item.isDivider ? null : item.Id"
label="Berth Recommendations"
variant="outlined"
density="comfortable"
multiple
chips
closable-chips
:loading="loadingBerths"
prepend-inner-icon="mdi-star"
@update:model-value="updateBerthRecommendations"
>
<template v-slot:item="{ props, item }">
<v-divider v-if="item.raw.isDivider" class="mt-2 mb-2">
<template v-slot:default>
<div class="text-caption text-medium-emphasis px-2">
{{ item.raw.letter }}
</div>
</template>
</v-divider>
<v-list-item
v-else
v-bind="props"
:title="item.raw['Mooring Number']"
/>
</template>
<template v-slot:chip="{ props, item }">
<v-chip
v-bind="props"
:text="item.raw['Mooring Number']"
size="small"
></v-chip>
</template>
</v-autocomplete>
</v-col>
<v-col cols="12" md="6">
<v-autocomplete <v-autocomplete
v-model="selectedBerths" v-model="selectedBerths"
:items="groupedBerths" :items="groupedBerths"
@ -1094,9 +1056,18 @@ const updateBerths = async (newBerths: number[]) => {
// Update original values // Update original values
originalBerths.value = [...newBerths]; originalBerths.value = [...newBerths];
// Show success message
if (toAdd.length > 0 || toRemove.length > 0) {
toast.success("Berths updated successfully");
}
} catch (error) { } catch (error) {
console.error("Failed to update berths:", error); console.error("Failed to update berths:", error);
toast.error("Failed to update berths. Please try again."); // Show more specific error message on mobile
const errorMessage = mobile.value
? "Could not update berths. Please check your connection and try again."
: "Failed to update berths. Please try again.";
toast.error(errorMessage);
// Revert to original values on error // Revert to original values on error
selectedBerths.value = [...originalBerths.value]; selectedBerths.value = [...originalBerths.value];
} }
@ -1302,6 +1273,20 @@ const onInterestUpdated = async () => {
} }
}; };
// Handle mobile save - call the save function directly without debounce
const handleMobileSave = () => {
console.log('Mobile save button clicked');
// Cancel any pending debounced saves
if (debouncedSaveInterest) {
debouncedSaveInterest.cancel();
}
if (autoSave) {
autoSave.cancel();
}
// Call save directly
saveInterest();
};
// Load berths when component mounts // Load berths when component mounts
onMounted(() => { onMounted(() => {
loadAvailableBerths(); loadAvailableBerths();

View File

@ -125,7 +125,7 @@
:headers="headers" :headers="headers"
:items="filteredInterests" :items="filteredInterests"
:search="search" :search="search"
:sort-by="[{ key: 'Created At', order: 'desc' }, { key: 'Full Name', order: 'asc' }]" :sort-by="[{ key: 'Id', order: 'desc' }]"
must-sort must-sort
hover hover
:loading="loading" :loading="loading"

View File

@ -29,6 +29,9 @@ export default defineEventHandler(async (event) => {
throw createError({ statusCode: 401, statusMessage: "unauthenticated" }); throw createError({ statusCode: 401, statusMessage: "unauthenticated" });
} }
// Set longer timeout for this endpoint to prevent 502 errors
event.node.res.setTimeout(60000); // 60 seconds
try { try {
const body = await readBody(event); const body = await readBody(event);
const { interestId } = body; const { interestId } = body;