Add Daily Digest notification preferences with custom time support

- Add daily_digest boolean and daily_digest_hour fields to NotificationPreference model
- Update HandleDailyDigest to check user preferences and custom notification times
- Change Daily Digest scheduler to run hourly (supports per-user custom times)
- Update notification service DTOs for new fields
- Add Daily Digest toggle and custom time to admin notification prefs page
- Fix notification handlers to only notify users at their designated hour

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-07 22:48:18 -06:00
parent 0ea0c766ea
commit f88409cfb4
7 changed files with 134 additions and 71 deletions
@@ -164,6 +164,16 @@ export default function NotificationPrefsPage() {
/>
),
},
{
accessorKey: 'daily_digest',
header: 'Daily Digest',
cell: ({ row }) => (
<Switch
checked={row.original.daily_digest}
onCheckedChange={() => handleToggle(row.original.id, 'daily_digest', row.original.daily_digest)}
/>
),
},
{
accessorKey: 'email_task_completed',
header: 'Email: Completed',
@@ -183,8 +193,8 @@ export default function NotificationPrefsPage() {
</div>
),
cell: ({ row }) => {
const { task_due_soon_hour, task_overdue_hour, warranty_expiring_hour } = row.original;
const hasCustomTimes = task_due_soon_hour !== null || task_overdue_hour !== null || warranty_expiring_hour !== null;
const { task_due_soon_hour, task_overdue_hour, warranty_expiring_hour, daily_digest_hour } = row.original;
const hasCustomTimes = task_due_soon_hour !== null || task_overdue_hour !== null || warranty_expiring_hour !== null || daily_digest_hour !== null;
if (!hasCustomTimes) {
return <span className="text-muted-foreground text-sm">Default</span>;
@@ -207,6 +217,11 @@ export default function NotificationPrefsPage() {
<span className="text-muted-foreground">Warranty:</span> {formatHour(warranty_expiring_hour)}
</div>
)}
{daily_digest_hour !== null && (
<div className="text-xs">
<span className="text-muted-foreground">Daily Digest:</span> {formatHour(daily_digest_hour)}
</div>
)}
</div>
);
},
+5
View File
@@ -597,12 +597,14 @@ export interface NotificationPreference {
task_assigned: boolean;
residence_shared: boolean;
warranty_expiring: boolean;
daily_digest: boolean;
// Email preferences
email_task_completed: boolean;
// Custom notification times (UTC hour 0-23, null means use system default)
task_due_soon_hour: number | null;
task_overdue_hour: number | null;
warranty_expiring_hour: number | null;
daily_digest_hour: number | null;
created_at: string;
updated_at: string;
}
@@ -622,8 +624,11 @@ export interface UpdateNotificationPrefRequest {
task_assigned?: boolean;
residence_shared?: boolean;
warranty_expiring?: boolean;
daily_digest?: boolean;
// Email preferences
email_task_completed?: boolean;
// Custom notification times
daily_digest_hour?: number | null;
}
// Notification Preferences API