feat: Grundriss-Feature, Listenansicht mit Bildern, Gewerbe-Label, Image-Pool
- Grundriss (FloorPlanSection): PropertyUnit.floorPlanUrl?, Property.floorPlanUrl?; FloorPlanSection in MatchDetail + PropertyDetail; FloorPlanUrlSection in NewListing-Formular - Listenansicht (MatchCardCompact): horizontales Layout mit 120px Bildstreifen, Score-Badge, Asset-Label-Overlay, alle 3 grünen Punkte, Anfrage-Button - Light Industrial → "Gewerbe" überall (NeedInput, NeedCardPreview, CriteriaReviewPanel, newListingConstants, MyListings, propertyHelpers) - "Zum Originalinserat"-Button nur bei Maison-Work-Objekten - Image-Pool: propertyImageResolver mit sequentiellem Pool-Index (keine doppelten Bilder), nur Innenaufnahmen, rotate()-Trick für Sub-Pools - Overlay-Labels (Objekttyp + Stadtteil) in LocationPreview + IntelligenceMatchCard Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import type { IMatchProvider, MatchFilters } from './IMatchProvider'
|
||||
import type { Match } from '../domain/match'
|
||||
|
||||
// Matches are computed dynamically via calculateScore so weights always reflect need.weightingProfile
|
||||
// Populated at startup by MockupNeedProvider via syncMatchesForNeed (deterministic IDs, no duplicates)
|
||||
export const matchStore: Match[] = []
|
||||
const store = matchStore
|
||||
|
||||
@@ -13,6 +13,9 @@ export const MockupMatchProvider: IMatchProvider = {
|
||||
if (filters?.minScore) results = results.filter(m => m.matchScore >= filters.minScore!)
|
||||
if (filters?.matchStrength) results = results.filter(m => m.matchStrength === filters.matchStrength)
|
||||
if (filters?.organizationId) results = results.filter(m => m.organizationId === filters.organizationId)
|
||||
// Deduplicate by id — guards against double-push during dev hot-reload
|
||||
const seen = new Set<string>()
|
||||
results = results.filter(m => { if (seen.has(m.id)) return false; seen.add(m.id); return true })
|
||||
return results.sort((a, b) => b.matchScore - a.matchScore)
|
||||
},
|
||||
async getById(id) {
|
||||
|
||||
@@ -37,7 +37,7 @@ export const MockupNeedProvider: INeedProvider = {
|
||||
},
|
||||
}
|
||||
|
||||
// Compute matches for all pre-existing needs so scores reflect their weightingProfile
|
||||
// Recompute matches for all pre-existing needs — replaces static mock matches so scores reflect need.weightingProfile
|
||||
for (const need of store) {
|
||||
generateMatchesForNeed(need)
|
||||
syncMatchesForNeed(need)
|
||||
}
|
||||
|
||||
@@ -4,10 +4,22 @@ import { mockPipelineItems } from '../mock-data/pipelineItems'
|
||||
|
||||
const STORAGE_KEY = 'property_match_pipeline_items'
|
||||
|
||||
const STAGE_MIGRATION: Record<string, PipelineStage> = {
|
||||
DISCOVERED: 'CONTACTED',
|
||||
QUALIFIED: 'CONTACTED',
|
||||
}
|
||||
|
||||
function migrateStage(stage: string): PipelineStage {
|
||||
return (STAGE_MIGRATION[stage] ?? stage) as PipelineStage
|
||||
}
|
||||
|
||||
function load(): PipelineItem[] {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY)
|
||||
if (raw) return JSON.parse(raw) as PipelineItem[]
|
||||
if (raw) {
|
||||
const items = JSON.parse(raw) as PipelineItem[]
|
||||
return items.map(i => ({ ...i, stage: migrateStage(i.stage) }))
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
return [...mockPipelineItems]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user