diff --git a/.claude/settings.json b/.claude/settings.json index e89494b4..490e7d82 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -21,7 +21,9 @@ "Bash(npx drizzle-kit *)", "Bash(pip install *)", "Bash(terraform fmt *)", - "Bash(gh label *)" + "Bash(gh label *)", + "Bash(python3 -c \"import psycopg2\")", + "Bash(python3 -c \"import psycopg\")" ], "deny": [ "Bash(npx drizzle-kit generate)", diff --git a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/AnalyticsView.tsx b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/AnalyticsView.tsx index b41971b9..383db091 100644 --- a/src/app/portfolio/[slug]/(portfolio)/your-projects/live/AnalyticsView.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/your-projects/live/AnalyticsView.tsx @@ -7,6 +7,7 @@ import { Card, CardContent } from "@/app/shadcn_components/ui/card"; import DampMouldRiskPanel from "./DampMouldRiskPanel"; import CompletionTrendsChart from "./CompletionTrendsChart"; import SurveyIssuesPanel from "./SurveyIssuesPanel"; +import QueriesReviewPanel from "./QueriesReviewPanel"; import ExcludedFromPipelinePanel from "./ExcludedFromPipelinePanel"; import GroupFilter, { type GroupNode } from "./GroupFilter"; import { STAGE_COLORS, STAGE_ORDER } from "./types"; @@ -430,7 +431,13 @@ export default function AnalyticsView({ onOpenTable={onOpenTable} /> - {/* Row 7: Excluded from Pipeline (Removed from Bookings / Removed from Program) */} + {/* Row 7: Queries / Review with Landlord (parked but still in programme) */} + + + {/* Row 8: Excluded from Pipeline (Removed from Bookings / Removed from Program) */} (null); const [search, setSearch] = useState<{ pc: string; refresh: number } | null>(null); + const [filter, setFilter] = useState(""); const [basket, setBasket] = useState({}); const [done, setDone] = useState<{ added: number; skipped: number } | null>(null); @@ -110,6 +111,7 @@ export default function AddPropertiesClient({ } setInputError(null); setDone(null); + setFilter(""); setSearch({ pc, refresh: 0 }); }; @@ -130,6 +132,12 @@ export default function AddPropertiesClient({ const addable = data ? data.candidates.filter((c) => c.selectable && !c.alreadyInPortfolio) : []; + const needle = filter.trim().toLowerCase(); + const visible = data + ? needle + ? data.candidates.filter((c) => c.address.toLowerCase().includes(needle)) + : data.candidates + : []; const totalSelected = countSelected(basket); const postcodes = Object.keys(basket); @@ -267,7 +275,9 @@ export default function AddPropertiesClient({

{data.postcode}

- {data.candidates.length}{" "} + {needle + ? `${visible.length} of ${data.candidates.length}` + : data.candidates.length}{" "} {data.candidates.length === 1 ? "address" : "addresses"} {addable.length > 0 && ( @@ -307,10 +317,40 @@ export default function AddPropertiesClient({
+ {/* Long postcodes can return ~100 addresses — a filter lets the user + jump to a house number or street name without scrolling. */} + {data.candidates.length > 8 && ( +
+ + setFilter(e.target.value)} + placeholder="Filter these addresses…" + spellCheck={false} + autoComplete="off" + className="w-full border border-slate-200 rounded-xl pl-9 pr-9 py-2 text-sm focus:outline-none focus:border-primary focus:ring-2 focus:ring-primary/10" + /> + {filter && ( + + )} +
+ )} + {/* Long postcodes can return ~100 addresses — scroll inside the card rather than growing the page. */}
- {data.candidates.map((c) => { + {visible.length === 0 ? ( +

+ No addresses match “{filter.trim()}”. +

+ ) : ( + visible.map((c) => { const { label, note } = describeCandidate(c); const disabled = !c.selectable || c.alreadyInPortfolio; const isSelected = !!selected[c.uprn]; @@ -361,7 +401,8 @@ export default function AddPropertiesClient({ ); - })} + }) + )}
)}