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:
@@ -0,0 +1,66 @@
|
||||
import { MockupReminderProvider } from '../provider/MockupReminderProvider'
|
||||
import type { Reminder } from '../domain/reminder'
|
||||
import { ReminderPriority, ReminderStatus, ShadowMarketRisk } from '../domain/reminder'
|
||||
|
||||
const provider = MockupReminderProvider
|
||||
|
||||
const MOCK_TODAY = new Date('2026-05-20')
|
||||
|
||||
function daysDiff(isoDate: string): number {
|
||||
const due = new Date(isoDate)
|
||||
return Math.ceil((due.getTime() - MOCK_TODAY.getTime()) / (1000 * 60 * 60 * 24))
|
||||
}
|
||||
|
||||
export const reminderService = {
|
||||
getAll: () => provider.getAll(),
|
||||
getById: (id: string) => provider.getById(id),
|
||||
update: (id: string, data: Partial<Reminder>) => provider.update(id, data),
|
||||
complete: (id: string, note?: string) => provider.complete(id, note),
|
||||
dismiss: (id: string, note?: string) => provider.dismiss(id, note),
|
||||
snooze: (id: string, until: string) => provider.snooze(id, until),
|
||||
create: (data: Omit<Reminder, 'id' | 'activity' | 'createdAt' | 'updatedAt'>) =>
|
||||
provider.create(data),
|
||||
|
||||
getInsights: async () => {
|
||||
const reminders = await provider.getAll()
|
||||
const active = reminders.filter(r => r.status === ReminderStatus.ACTIVE || r.status === ReminderStatus.SNOOZED)
|
||||
|
||||
const urgentCount = active.filter(r => r.priority === ReminderPriority.URGENT).length
|
||||
|
||||
const endOfWeek = new Date(MOCK_TODAY)
|
||||
endOfWeek.setDate(endOfWeek.getDate() + 7)
|
||||
const dueThisWeek = active.filter(r => {
|
||||
const d = daysDiff(r.dueDate)
|
||||
return d >= 0 && d <= 7
|
||||
}).length
|
||||
|
||||
const endOfMonth = new Date(MOCK_TODAY)
|
||||
endOfMonth.setDate(endOfMonth.getDate() + 30)
|
||||
const dueThisMonth = active.filter(r => {
|
||||
const d = daysDiff(r.dueDate)
|
||||
return d >= 0 && d <= 30
|
||||
}).length
|
||||
|
||||
const schattenmarktReadyCount = reminders.filter(
|
||||
r =>
|
||||
r.status === ReminderStatus.ACTIVE &&
|
||||
!r.schattenmarktEnabled &&
|
||||
(r.shadowMarketRisk === ShadowMarketRisk.HIGH),
|
||||
).length
|
||||
|
||||
const activeDays = active
|
||||
.filter(r => daysDiff(r.dueDate) > 0)
|
||||
.map(r => daysDiff(r.dueDate))
|
||||
const avgDaysToAction = activeDays.length
|
||||
? Math.round(activeDays.reduce((s, d) => s + d, 0) / activeDays.length)
|
||||
: 0
|
||||
|
||||
return {
|
||||
urgentCount,
|
||||
dueThisWeek,
|
||||
dueThisMonth,
|
||||
schattenmarktReadyCount,
|
||||
avgDaysToAction,
|
||||
}
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user