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
+11
View File
@@ -0,0 +1,11 @@
import type { Reminder } from '../domain/reminder'
export interface IReminderProvider {
getAll(): Promise<Reminder[]>
getById(id: string): Promise<Reminder | null>
update(id: string, data: Partial<Reminder>): Promise<Reminder>
complete(id: string, note?: string): Promise<Reminder>
dismiss(id: string, note?: string): Promise<Reminder>
snooze(id: string, until: string): Promise<Reminder>
create(data: Omit<Reminder, 'id' | 'activity' | 'createdAt' | 'updatedAt'>): Promise<Reminder>
}
+73
View File
@@ -0,0 +1,73 @@
import { mockReminders } from '../mock-data/reminders'
import type { Reminder, ReminderActivity } from '../domain/reminder'
import { ReminderStatus } from '../domain/reminder'
import type { IReminderProvider } from './IReminderProvider'
let store: Reminder[] = [...mockReminders]
function now(): string {
return new Date().toISOString()
}
function addActivity(reminder: Reminder, entry: ReminderActivity): Reminder {
return { ...reminder, activity: [...reminder.activity, entry], updatedAt: now() }
}
export const MockupReminderProvider: IReminderProvider = {
async getAll() {
return [...store]
},
async getById(id) {
return store.find(r => r.id === id) ?? null
},
async update(id, data) {
const idx = store.findIndex(r => r.id === id)
if (idx === -1) throw new Error(`Reminder ${id} not found`)
store[idx] = { ...store[idx], ...data, updatedAt: now() }
return store[idx]
},
async complete(id, note) {
const idx = store.findIndex(r => r.id === id)
if (idx === -1) throw new Error(`Reminder ${id} not found`)
store[idx] = addActivity(
{ ...store[idx], status: ReminderStatus.COMPLETED },
{ at: now(), by: 'current-user', action: 'COMPLETED', note },
)
return store[idx]
},
async dismiss(id, note) {
const idx = store.findIndex(r => r.id === id)
if (idx === -1) throw new Error(`Reminder ${id} not found`)
store[idx] = addActivity(
{ ...store[idx], status: ReminderStatus.DISMISSED },
{ at: now(), by: 'current-user', action: 'DISMISSED', note },
)
return store[idx]
},
async snooze(id, until) {
const idx = store.findIndex(r => r.id === id)
if (idx === -1) throw new Error(`Reminder ${id} not found`)
store[idx] = addActivity(
{ ...store[idx], status: ReminderStatus.SNOOZED, snoozedUntil: until },
{ at: now(), by: 'current-user', action: 'SNOOZED', note: `Snoozed until ${until}` },
)
return store[idx]
},
async create(data) {
const reminder: Reminder = {
...data,
id: crypto.randomUUID(),
activity: [{ at: now(), by: 'current-user', action: 'CREATED' }],
createdAt: now(),
updatedAt: now(),
}
store.push(reminder)
return reminder
},
}