feat: Reminder Manager + Schattenmarkt-Freigabe + mock data overhaul

- Add Reminder Manager page (/supply/reminder-manager) with KPI bar,
  filter bar, list/card feed, and detail drawer (7 sections incl.
  activity log, snooze, complete, dismiss actions)
- Add Schattenmarkt-Freigabe toggle on PropertyDetailView: Verwaltung
  can opt-in properties for early market exposure before contract expiry
- Auto-generate FutureSignal cards via useSchattenmarktSignals hook
  when leaseEndDate - leadTimeMonths <= MOCK_TODAY
- Fix useUnifiedResults: use property.resultType as fallback (was
  defaulting everything to VERIFIED_PORTFOLIO)
- Fix demand Results: hide VERIFIED_PORTFOLIO from non-manager users
  even when showOwnProperties toggle was previously enabled
- Fix AppShell: redirect to allowed workspace on user role switch
- Fix 3 wrong match scores (match-002: 93→62, match-009: 86→55,
  match-017: 87→52)
- Add 7 new match records (match-050–056) for need-001, need-002,
  need-011
- Add need-011 (Retail Bern Innenstadt)
- Add prop-031–036 (EXTERNAL_MARKET / MAISON_WORK / FUTURE_AVAILABILITY)
- Fix duplicate image URLs across all properties
- Add signal-011 (Bern Altstadt, Mode Boutique) + propertyId to
  signal-001

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Sutter
2026-05-20 11:52:24 +02:00
parent 0663757cde
commit 9f391d17cb
36 changed files with 3011 additions and 71 deletions
+1
View File
@@ -161,6 +161,7 @@ export const SignalType = {
RESTRUCTURING: 'RESTRUCTURING',
PROJECT_DEVELOPMENT: 'PROJECT_DEVELOPMENT',
SPACE_CONSOLIDATION: 'SPACE_CONSOLIDATION',
LEASE_EXPIRY: 'LEASE_EXPIRY',
} as const
export type SignalType = typeof SignalType[keyof typeof SignalType]
+1 -1
View File
@@ -1,7 +1,7 @@
import type { SignalType, RiskLevel, ReviewStatus } from './enums'
export interface SignalSource {
type: 'PRESS' | 'CONSTRUCTION_PERMIT' | 'JOB_POSTING' | 'COMPANY_REPORT' | 'MARKET_DATA' | 'MANUAL'
type: 'PRESS' | 'CONSTRUCTION_PERMIT' | 'JOB_POSTING' | 'COMPANY_REPORT' | 'MARKET_DATA' | 'MANUAL' | 'LEASE_CONTRACT'
url?: string
publishedAt?: string
credibility: 'LOW' | 'MEDIUM' | 'HIGH'
+2
View File
@@ -180,6 +180,8 @@ export interface Property {
importedAt?: string
lastUpdatedAt?: string
schattenmarktRelease?: { enabled: boolean; leadTimeMonths: number }
status?: 'ACTIVE' | 'INACTIVE' | 'DRAFT' | 'ARCHIVED'
lastReviewedAt?: string
createdAt: string
+76
View File
@@ -0,0 +1,76 @@
export const ReminderType = {
LEASE_EXPIRY: 'LEASE_EXPIRY',
BREAK_OPTION: 'BREAK_OPTION',
RENT_REVIEW: 'RENT_REVIEW',
INSPECTION: 'INSPECTION',
INSURANCE_RENEWAL: 'INSURANCE_RENEWAL',
MAINTENANCE: 'MAINTENANCE',
SCHATTENMARKT_RELEASE: 'SCHATTENMARKT_RELEASE',
CUSTOM: 'CUSTOM',
} as const
export type ReminderType = typeof ReminderType[keyof typeof ReminderType]
export const ReminderPriority = {
URGENT: 'URGENT', // ≤14 days
HIGH: 'HIGH', // 1530 days
MEDIUM: 'MEDIUM', // 3160 days
LOW: 'LOW', // >60 days
} as const
export type ReminderPriority = typeof ReminderPriority[keyof typeof ReminderPriority]
export const ReminderStatus = {
ACTIVE: 'ACTIVE',
SNOOZED: 'SNOOZED',
COMPLETED: 'COMPLETED',
DISMISSED: 'DISMISSED',
} as const
export type ReminderStatus = typeof ReminderStatus[keyof typeof ReminderStatus]
export const ShadowMarketRisk = {
NONE: 'NONE',
LOW: 'LOW',
MEDIUM: 'MEDIUM',
HIGH: 'HIGH',
} as const
export type ShadowMarketRisk = typeof ShadowMarketRisk[keyof typeof ShadowMarketRisk]
export interface ReminderActivity {
at: string // ISO date
by: string // user display name
action: 'CREATED' | 'SNOOZED' | 'COMPLETED' | 'DISMISSED' | 'NOTED'
note?: string
}
export interface Reminder {
id: string
type: ReminderType
priority: ReminderPriority
status: ReminderStatus
propertyId: string
propertyTitle: string
propertyCity: string
propertyDistrict?: string
tenantName: string
dueDate: string // ISO date — when action must be taken
eventDate: string // ISO date — contract event date (expiry / review / etc.)
contractEndDate?: string // ISO date — lease end
breakOptionDate?: string // ISO date
areaSqm: number
currentRentPerSqm: number
currency: 'CHF'
shadowMarketRisk: ShadowMarketRisk
schattenmarktEnabled: boolean
note?: string
snoozedUntil?: string // ISO date
activity: ReminderActivity[]
organizationId: string
createdAt: string
updatedAt: string
}