d2af8664f4
- reminderService: dueThisWeek/dueThisMonth use exclusive day ranges (1-7, 8-30) matching getHorizon() buckets; schattenmarktReadyCount counts by type=SCHATTENMARKT_RELEASE (same criteria as the feed filter) - ReminderKpiBar: clicking a horizon card resets filterType to ALL; clicking Pre-Market card resets filterHorizon to ALL — ensures the number shown on the card always equals the number of rows displayed below Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
64 lines
2.2 KiB
TypeScript
64 lines
2.2 KiB
TypeScript
import { MockupReminderProvider } from '../provider/MockupReminderProvider'
|
|
import type { Reminder } from '../domain/reminder'
|
|
import { ReminderPriority, ReminderStatus, ReminderType } 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
|
|
|
|
// Counts match getHorizon() in ReminderFeed — exclusive, non-overlapping buckets
|
|
const dueThisWeek = active.filter(r => {
|
|
const d = daysDiff(r.dueDate)
|
|
return d >= 1 && d <= 7
|
|
}).length
|
|
|
|
const dueThisMonth = active.filter(r => {
|
|
const d = daysDiff(r.dueDate)
|
|
return d >= 8 && d <= 30
|
|
}).length
|
|
|
|
const schattenmarktReadyCount = active.filter(
|
|
r => r.type === ReminderType.SCHATTENMARKT_RELEASE,
|
|
).length
|
|
|
|
const overdueCount = active.filter(r => daysDiff(r.dueDate) <= 0).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,
|
|
overdueCount,
|
|
avgDaysToAction,
|
|
}
|
|
},
|
|
}
|