diff --git a/src/app/portfolio/[slug]/(portfolio)/settings/PortfolioSettings.tsx b/src/app/portfolio/[slug]/(portfolio)/settings/PortfolioSettings.tsx index 89930940..82453202 100644 --- a/src/app/portfolio/[slug]/(portfolio)/settings/PortfolioSettings.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/settings/PortfolioSettings.tsx @@ -281,18 +281,28 @@ export default function PortfolioSettings({ setIsDeleteModalOpen(true); } - function handleDeleteConfirmation() { - if (deleteConfirmationByName === portfolioSettingsData.name) { - mutateDelete({ + async function handleDeleteConfirmation() { + if (deleteConfirmationByName !== portfolioSettingsData.name) { + console.warn("Delete confirmation name does not match"); + return; + } + + try { + console.log("[DELETE] starting delete mutation"); + + await mutateDelete({ userId, portfolioId, }); - console.log("succesfully called the mututate function"); + + console.log("[DELETE] mutation completed successfully"); + // Refresh table / page data + router.refresh(); + } catch (err) { + console.error("[DELETE] mutation failed", err); } } - // RENAMING FUNCTIONS - // Change NAME functionality - changing state function handlePortfolioNameChange(e: React.ChangeEvent) { @@ -460,7 +470,7 @@ export default function PortfolioSettings({ - +
Danger Zone: diff --git a/src/app/portfolio/[slug]/components/PropertyTable.tsx b/src/app/portfolio/[slug]/components/PropertyTable.tsx index dd6ba60f..d8ee22e1 100644 --- a/src/app/portfolio/[slug]/components/PropertyTable.tsx +++ b/src/app/portfolio/[slug]/components/PropertyTable.tsx @@ -1,6 +1,7 @@ "use client"; import { useState, useMemo, useEffect } from "react"; +import { useRouter } from "next/navigation"; import { useProperties } from "./useProperties"; import DataTable from "./dataTable"; import PropertyFilters, { PropertyFilterValues } from "./PropertyFilters"; @@ -86,6 +87,8 @@ export default function PropertyTable({ }: { portfolioId: string; }) { + const router = useRouter(); + const [filters, setFilters] = useState({ address: "", postcode: "", @@ -101,6 +104,7 @@ export default function PropertyTable({ isLoading, isFetching, isError, + refetch, } = useProperties({ portfolioId, filters: parsedFilters, @@ -123,8 +127,6 @@ export default function PropertyTable({ useEffect(() => { if (!deletePropertyId) return; - console.log("[PREVIEW] fetching delete preview for:", deletePropertyId); - setPreviewLoading(true); setPreviewError(null); setDeletePreview(null); @@ -133,50 +135,37 @@ export default function PropertyTable({ method: "POST", }) .then(async (res) => { - if (!res.ok) { - throw new Error("Failed to fetch delete preview"); - } + if (!res.ok) throw new Error("Failed to fetch delete preview"); return res.json(); }) - .then((data) => { - console.log("[PREVIEW] result:", data.preview); - setDeletePreview(data.preview); - }) - .catch((err) => { - console.error("[PREVIEW] error:", err); - setPreviewError(err.message); - }) - .finally(() => { - setPreviewLoading(false); - }); + .then((data) => setDeletePreview(data.preview)) + .catch((err) => setPreviewError(err.message)) + .finally(() => setPreviewLoading(false)); }, [deletePropertyId]); + /* ---------------------------------------- + Confirm delete + ----------------------------------------- */ const handleConfirmDelete = async () => { if (!deletePropertyId) return; setDeleteLoading(true); try { - console.log("[CONFIRM DELETE] sending request…"); + await fetch(`/api/properties/${deletePropertyId}/delete/confirm`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ confirm: true }), + }); - const res = await fetch( - `/api/properties/${deletePropertyId}/delete/confirm`, - { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ confirm: true }), - } - ); - - const data = await res.json(); - - console.log("[CONFIRM DELETE] response:", data); - - // TEMP: just close modal after backend confirms + // Close modal setDeletePropertyId(null); setDeletePreview(null); + + // ✅ THIS FIXES THE GHOST ROW + await refetch(); } catch (err) { - console.error("[CONFIRM DELETE] error:", err); + console.error("[DELETE] failed", err); } finally { setDeleteLoading(false); } @@ -186,24 +175,20 @@ export default function PropertyTable({
- {/* Filters */} - {/* Loading bar */} {isFetching && (
)} - {/* Filter info */} {hasActiveFilters && !isFetching && (
Filters applied ({parsedFilters.length})
)} - {/* Content */} {isLoading ? (
Loading properties…
) : isError ? ( @@ -231,10 +216,7 @@ export default function PropertyTable({ { - console.log("[META] onDeleteProperty fired:", propertyId); - setDeletePropertyId(propertyId); - }} + onDeleteProperty={(id) => setDeletePropertyId(id)} /> )}
@@ -263,7 +245,6 @@ export default function PropertyTable({ - {/* Loading */} {previewLoading && (
@@ -273,12 +254,10 @@ export default function PropertyTable({
)} - {/* Error */} {previewError && (

{previewError}

)} - {/* Preview result */} {deletePreview && !previewLoading && (
    {deletePreview.map((row) => ( @@ -302,19 +281,13 @@ export default function PropertyTable({ > Cancel + diff --git a/src/lib/services/propertyDeletion.ts b/src/lib/services/propertyDeletion.ts index 83cb8370..8d4757b9 100644 --- a/src/lib/services/propertyDeletion.ts +++ b/src/lib/services/propertyDeletion.ts @@ -5,17 +5,22 @@ export async function previewPropertyDeletion(propertyId: number) { const id = sql`${propertyId}`; const result = await db.execute(sql` + -- --------------------------------- + -- Recommendation chain + -- --------------------------------- SELECT 'recommendation_materials' AS table, COUNT(*)::int AS count FROM recommendation_materials rm JOIN recommendation r ON rm.recommendation_id = r.id WHERE r.property_id = ${id} UNION ALL - SELECT 'plan_recommendations', COUNT(*) - FROM plan_recommendations pr - JOIN plan p ON pr.plan_id = p.id - WHERE p.property_id = ${id} + SELECT 'recommendation', COUNT(*) + FROM recommendation + WHERE property_id = ${id} + -- --------------------------------- + -- Funding / plan chain + -- --------------------------------- UNION ALL SELECT 'funding_package_measures', COUNT(*) FROM funding_package_measures fpm @@ -24,20 +29,53 @@ export async function previewPropertyDeletion(propertyId: number) { WHERE p.property_id = ${id} UNION ALL - SELECT 'inspections', COUNT(*) - FROM inspections - WHERE property_id = ${id} + SELECT 'funding_package', COUNT(*) + FROM funding_package fp + JOIN plan p ON fp.plan_id = p.id + WHERE p.property_id = ${id} UNION ALL - SELECT 'recommendation', COUNT(*) - FROM recommendation - WHERE property_id = ${id} + SELECT 'plan_recommendations', COUNT(*) + FROM plan_recommendations pr + JOIN plan p ON pr.plan_id = p.id + WHERE p.property_id = ${id} UNION ALL SELECT 'plan', COUNT(*) FROM plan WHERE property_id = ${id} + -- --------------------------------- + -- Property direct children + -- --------------------------------- + UNION ALL + SELECT 'property_details_epc', COUNT(*) + FROM property_details_epc + WHERE property_id = ${id} + + UNION ALL + SELECT 'property_targets', COUNT(*) + FROM property_targets + WHERE property_id = ${id} + + UNION ALL + SELECT 'property_status_tracker', COUNT(*) + FROM property_status_tracker + WHERE property_id = ${id} + + UNION ALL + SELECT 'inspections', COUNT(*) + FROM inspections + WHERE property_id = ${id} + + UNION ALL + SELECT 'files_from_surveyor', COUNT(*) + FROM files_from_surveyor + WHERE property_id = ${id} + + -- --------------------------------- + -- Root + -- --------------------------------- UNION ALL SELECT 'property', COUNT(*) FROM property @@ -49,6 +87,47 @@ export async function previewPropertyDeletion(propertyId: number) { export async function deleteProperty(propertyId: number) { await db.transaction(async (tx) => { + // ----------------------------- + // LEAF TABLES FIRST + // ----------------------------- + + await tx.execute(sql` + DELETE FROM funding_package_measures fpm + USING funding_package fp, plan p + WHERE fpm.funding_package_id = fp.id + AND fp.plan_id = p.id + AND p.property_id = ${propertyId}; + `); + + await tx.execute(sql` + DELETE FROM recommendation_materials rm + USING recommendation r + WHERE rm.recommendation_id = r.id + AND r.property_id = ${propertyId}; + `); + + // ----------------------------- + // PLAN CHILDREN + // ----------------------------- + + await tx.execute(sql` + DELETE FROM funding_package fp + USING plan p + WHERE fp.plan_id = p.id + AND p.property_id = ${propertyId}; + `); + + await tx.execute(sql` + DELETE FROM plan_recommendations pr + USING plan p + WHERE pr.plan_id = p.id + AND p.property_id = ${propertyId}; + `); + + // ----------------------------- + // PROPERTY DIRECT CHILDREN + // ----------------------------- + await tx.execute(sql` DELETE FROM property_details_epc WHERE property_id = ${propertyId}; @@ -60,25 +139,8 @@ export async function deleteProperty(propertyId: number) { `); await tx.execute(sql` - DELETE FROM recommendation_materials rm - USING recommendation r - WHERE rm.recommendation_id = r.id - AND r.property_id = ${propertyId}; - `); - - await tx.execute(sql` - DELETE FROM plan_recommendations pr - USING plan p - WHERE pr.plan_id = p.id - AND p.property_id = ${propertyId}; - `); - - await tx.execute(sql` - DELETE FROM funding_package_measures fpm - USING funding_package fp, plan p - WHERE fpm.funding_package_id = fp.id - AND fp.plan_id = p.id - AND p.property_id = ${propertyId}; + DELETE FROM property_status_tracker + WHERE property_id = ${propertyId}; `); await tx.execute(sql` @@ -86,6 +148,11 @@ export async function deleteProperty(propertyId: number) { WHERE property_id = ${propertyId}; `); + await tx.execute(sql` + DELETE FROM files_from_surveyor + WHERE property_id = ${propertyId}; + `); + await tx.execute(sql` DELETE FROM recommendation WHERE property_id = ${propertyId}; @@ -96,9 +163,27 @@ export async function deleteProperty(propertyId: number) { WHERE property_id = ${propertyId}; `); + // ----------------------------- + // PROPERTY LAST (ALWAYS) + // ----------------------------- + await tx.execute(sql` DELETE FROM property WHERE id = ${propertyId}; `); }); } + +// Find All foregin keys used for 'property' table with id via this command +// SELECT +// tc.table_name, +// kcu.column_name, +// ccu.table_name AS foreign_table_name, +// ccu.column_name AS foreign_column_name +// FROM information_schema.table_constraints AS tc +// JOIN information_schema.key_column_usage AS kcu +// ON tc.constraint_name = kcu.constraint_name +// JOIN information_schema.constraint_column_usage AS ccu +// ON ccu.constraint_name = tc.constraint_name +// WHERE tc.constraint_type = 'FOREIGN KEY' +// AND ccu.table_name = 'property';