feat: Verwaltungszugang refactor — lease data, 3-tab detail, market signals
- Dashboard: remove QuickActionPanel, StrongMatchOverview, FutureSignalWidget,
DataQualityWidget, header buttons, Marktchancen nav; KpiGrid → 4 cards
- Property domain: 9 new fields (leaseTerm, leaseStartDate/End, breakoutOption,
currentTenant, importedFrom, importedAt, lastUpdatedAt)
- Mock data: annual CHF/m²/Jahr prices (×12), Swiss images, tenant/lease data
for all 10 VERIFIED_PORTFOLIO properties; need budgets updated to annual
- PropertyTable: swap columns — add Aktueller Mieter, Mietlaufzeit,
Breakoutoption, Breakoutoption Zeitpunkt; remove Verfügbarkeit, Konfidenz, Quelle
- PropertyDetailView: 3 tabs (Übersicht, Matchability, Marktsignale) with
inline edit mode, NeedMatchCard list, PropertyMarketSignalsTab
- New: marketReport domain + mock data + service, NeedMatchCard,
PropertyMarketSignalsTab with 4 sections + PDF button
- matchService: getNeedMatchesForProperty(propertyId, {minScore})
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
import type { MarketReport } from '../domain/marketReport'
|
||||
import { mockPropertyMarketSignals } from '../mock-data/propertyMarketSignals'
|
||||
|
||||
export const marketReportService = {
|
||||
async getByProperty(propertyId: string): Promise<MarketReport | null> {
|
||||
return mockPropertyMarketSignals.find(r => r.propertyId === propertyId) ?? null
|
||||
},
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { MockupMatchProvider } from '../provider/MockupMatchProvider'
|
||||
import { MockupPropertyProvider } from '../provider/MockupPropertyProvider'
|
||||
import { MockupNeedProvider } from '../provider/MockupNeedProvider'
|
||||
import type { MatchFilters } from '../provider/IMatchProvider'
|
||||
import type { Match } from '../domain/match'
|
||||
import type { Match, PropertyNeedMatch } from '../domain/match'
|
||||
import type { Need } from '../domain/need'
|
||||
import type { Property } from '../domain/property'
|
||||
import type { StrongMatchItem } from '../domain/dashboard'
|
||||
@@ -65,6 +65,39 @@ export const matchService = {
|
||||
return { data, meta: { total: data.length, page: 1, pageSize: data.length, hasMore: false } }
|
||||
},
|
||||
|
||||
async getNeedMatchesForProperty(
|
||||
propertyId: string,
|
||||
opts?: { minScore?: number },
|
||||
): Promise<PropertyNeedMatch[]> {
|
||||
const minScore = opts?.minScore ?? 80
|
||||
const [matches, needs] = await Promise.all([
|
||||
provider.getByProperty(propertyId),
|
||||
MockupNeedProvider.getAll(),
|
||||
])
|
||||
return matches
|
||||
.filter(m => m.matchScore >= minScore)
|
||||
.sort((a, b) => b.matchScore - a.matchScore)
|
||||
.map(m => {
|
||||
const need = needs.find(n => n.id === m.needId)
|
||||
if (!need) return null
|
||||
return {
|
||||
matchId: m.id,
|
||||
score: m.matchScore,
|
||||
needId: need.id,
|
||||
needTitle: `${need.companyName}`,
|
||||
company: need.companyName,
|
||||
areaRequired: `${need.requiredArea.min}–${need.requiredArea.max} m²`,
|
||||
budget: `max ${need.budgetRange.maxPerSqm} CHF/m²/Jahr`,
|
||||
locations: need.preferredLocations,
|
||||
assetType: need.assetType,
|
||||
timing: need.timing?.earliestMoveIn ?? '–',
|
||||
mustHaves: need.mustCriteriaText ?? [],
|
||||
matchHighlights: m.positiveFactors.map(f => f.explanation),
|
||||
} satisfies PropertyNeedMatch
|
||||
})
|
||||
.filter((x): x is PropertyNeedMatch => x !== null)
|
||||
},
|
||||
|
||||
async getStrongMatches(minScore = 80): Promise<StrongMatchItem[]> {
|
||||
const [matches, properties] = await Promise.all([
|
||||
provider.getAll(),
|
||||
|
||||
Reference in New Issue
Block a user