diff --git a/components/InterestDetailsModal.vue b/components/InterestDetailsModal.vue index 44d9cd3..a5cf669 100644 --- a/components/InterestDetailsModal.vue +++ b/components/InterestDetailsModal.vue @@ -23,7 +23,8 @@ :disabled=" isRequestingMoreInfo || isRequestingMoreInformation || - isSendingEOI + isSendingEOI || + isDeleting " > mdi-information-outline @@ -36,7 +37,8 @@ :disabled=" isRequestingMoreInfo || isRequestingMoreInformation || - isSendingEOI + isSendingEOI || + isDeleting " > mdi-email-outline @@ -49,19 +51,37 @@ :disabled=" isRequestingMoreInfo || isRequestingMoreInformation || - isSendingEOI + isSendingEOI || + isDeleting " > mdi-send EOI to Sales + + mdi-delete + Delete + mdi-content-save @@ -340,11 +360,62 @@ + + + + mdi-chart-timeline-variant + Sales Pipeline Status + + + + + + + + +
+ +
+ Progress: {{ currentStep + 1 }} of {{ InterestSalesProcessLevelFlow.length }} stages +
+
+
+
+
+
+ mdi-anchor - Berth & Sales Information + Berth Information @@ -357,16 +428,6 @@ prepend-inner-icon="mdi-tape-measure" > - - - ([]); @@ -962,6 +1024,62 @@ const formatDate = (dateString: string | null | undefined) => { } }; +// Get color for sales level +const getSalesLevelColor = (level: string) => { + switch (level?.toLowerCase()) { + case "initial inquiry": + return "grey"; + case "qualified interest": + return "blue"; + case "eoi sent": + return "orange"; + case "loi sent": + return "purple"; + case "reservation agreement sent": + return "green"; + case "reserved": + return "success"; + default: + return "grey"; + } +}; + +// Confirm delete +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(); + } +}; + +// Delete interest +const deleteInterest = async () => { + if (!interest.value) return; + + isDeleting.value = true; + try { + await $fetch("/api/delete-interest", { + method: "POST", + headers: { + "x-tag": user.value?.email ? "094ut234" : "pjnvü1230", + }, + body: { + id: interest.value.Id.toString(), + }, + }); + + 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."); + } finally { + isDeleting.value = false; + } +}; + // Load berths when component mounts onMounted(() => { loadAvailableBerths(); diff --git a/pages/dashboard/file-browser.vue b/pages/dashboard/file-browser.vue index 6ea733c..0915481 100644 --- a/pages/dashboard/file-browser.vue +++ b/pages/dashboard/file-browser.vue @@ -4,7 +4,6 @@

- mdi-folder File Browser

diff --git a/pages/dashboard/interest-list.vue b/pages/dashboard/interest-list.vue index 18ecf66..fe87176 100644 --- a/pages/dashboard/interest-list.vue +++ b/pages/dashboard/interest-list.vue @@ -52,7 +52,17 @@ - + + + Clear Filters + -

-
{{ item["Full Name"] }}
+
+
+ {{ item["Full Name"] }} + + + {{ item["Extra Comments"] }} + +
{{ item["Email Address"] }}
- - - {{ item["Berth Number"] }} - - - - - - - - - - - - - - - {{ getRelativeTime(item["Created At"]) }} - - - - {{ item["Extra Comments"] }} - - - @@ -230,9 +205,6 @@ import InterestSalesBadge from "~/components/InterestSalesBadge.vue"; import InterestDetailsModal from "~/components/InterestDetailsModal.vue"; import CreateInterestModal from "~/components/CreateInterestModal.vue"; import EOIStatusBadge from "~/components/EOIStatusBadge.vue"; -import BerthInfoSentStatusBadge from "~/components/BerthInfoSentStatusBadge.vue"; -import ContractSentStatusBadge from "~/components/ContractSentStatusBadge.vue"; -import Deposit10PercentStatusBadge from "~/components/Deposit10PercentStatusBadge.vue"; import ContractStatusBadge from "~/components/ContractStatusBadge.vue"; import { useFetch } from "#app"; import { ref, computed } from "vue"; @@ -285,19 +257,25 @@ const handleInterestCreated = async (interest: Interest) => { }; const headers = [ - { title: "Contact", key: "Full Name", sortable: true, width: "20%" }, - { title: "Berth", key: "Berth Number", sortable: true }, + { title: "Contact", key: "Full Name", sortable: true, width: "25%" }, { title: "Sales Status", key: "Sales Process Level", sortable: true }, { title: "EOI Status", key: "EOI Status", sortable: true }, - { title: "Berth Info", key: "Berth Info Sent Status", sortable: true }, - { title: "Contract Sent", key: "Contract Sent Status", sortable: true }, - { title: "Deposit 10%", key: "Deposit 10% Status", sortable: true }, { title: "Contract", key: "Contract Status", sortable: true }, { title: "Category", key: "Lead Category", sortable: true }, { title: "Created", key: "Created At", sortable: true }, - { title: "", key: "Extra Comments", sortable: false }, ]; +// Check if any filters are active +const hasActiveFilters = computed(() => { + return search.value !== '' || selectedSalesLevel.value !== 'all'; +}); + +// Clear all filters +const clearAllFilters = () => { + search.value = ''; + selectedSalesLevel.value = 'all'; +}; + const formatDate = (dateString: string) => { if (!dateString) return "-"; @@ -481,12 +459,86 @@ const getRelativeTime = (dateString: string) => { object-fit: contain; } -/* Mobile horizontal scrolling */ +/* Mobile horizontal scrolling with visual indicators */ .table-container { + position: relative; overflow-x: auto; -webkit-overflow-scrolling: touch; } +/* Scroll indicators */ +.table-container::before, +.table-container::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + width: 40px; + pointer-events: none; + z-index: 1; + transition: opacity 0.3s; +} + +.table-container::before { + left: 0; + background: linear-gradient(to right, white, transparent); + opacity: 0; +} + +.table-container::after { + right: 0; + background: linear-gradient(to left, white, transparent); +} + +.table-container:not(.scroll-start)::before { + opacity: 1; +} + +.table-container:not(.scroll-end)::after { + opacity: 1; +} + +/* Safari-specific fixes */ +.modern-table :deep(.v-table__wrapper) { + -webkit-transform: translateZ(0); + transform: translateZ(0); +} + +.modern-table :deep(.v-data-table__td) { + min-width: 0; +} + +/* Column width constraints */ +.modern-table :deep(th:nth-child(1)), +.modern-table :deep(td:nth-child(1)) { + min-width: 250px; +} + +.modern-table :deep(th:nth-child(2)), +.modern-table :deep(td:nth-child(2)) { + min-width: 150px; +} + +.modern-table :deep(th:nth-child(3)), +.modern-table :deep(td:nth-child(3)) { + min-width: 120px; +} + +.modern-table :deep(th:nth-child(4)), +.modern-table :deep(td:nth-child(4)) { + min-width: 120px; +} + +.modern-table :deep(th:nth-child(5)), +.modern-table :deep(td:nth-child(5)) { + min-width: 120px; +} + +.modern-table :deep(th:nth-child(6)), +.modern-table :deep(td:nth-child(6)) { + min-width: 140px; +} + @media (max-width: 768px) { .table-container { margin: 0 -12px; @@ -494,7 +546,7 @@ const getRelativeTime = (dateString: string) => { } .modern-table :deep(.v-table__wrapper) { - min-width: 1200px; + min-width: 900px; } .modern-table :deep(th) { diff --git a/server/api/delete-interest.ts b/server/api/delete-interest.ts new file mode 100644 index 0000000..df48366 --- /dev/null +++ b/server/api/delete-interest.ts @@ -0,0 +1,22 @@ +import { deleteInterest } from "~/server/utils/nocodb"; + +export default defineEventHandler(async (event) => { + const body = await readBody(event); + const { id } = body; + const xTag = getHeader(event, "x-tag"); + + try { + // Delete the interest from NocoDB + await deleteInterest(id); + + return { + success: true, + message: "Interest deleted successfully", + }; + } catch (error: any) { + throw createError({ + statusCode: 500, + statusMessage: error.message || "Failed to delete interest", + }); + } +}); diff --git a/server/utils/nocodb.ts b/server/utils/nocodb.ts index cb34cdc..e0c1c86 100644 --- a/server/utils/nocodb.ts +++ b/server/utils/nocodb.ts @@ -79,7 +79,7 @@ export const updateInterest = async (id: string, data: Partial) => { // Filter the data to only include allowed fields for (const field of allowedFields) { if (field in data) { - cleanData[field] = data[field]; + cleanData[field] = (data as any)[field]; } } @@ -127,7 +127,7 @@ export const createInterest = async (data: Partial) => { // Filter the data to only include allowed fields for (const field of allowedFields) { if (field in data) { - cleanData[field] = data[field]; + cleanData[field] = (data as any)[field]; } } @@ -146,6 +146,14 @@ export const createInterest = async (data: Partial) => { }); }; +export const deleteInterest = async (id: string) => + $fetch(`${createTableUrl(Table.Interest)}/${id}`, { + method: "DELETE", + headers: { + "xc-token": getNocoDbConfiguration().token, + }, + }); + export const triggerWebhook = async (url: string, payload: any) => $fetch(url, { method: "POST",