Add per-user notification time preferences
Allow users to customize when they receive notification reminders: - Add task_due_soon_hour, task_overdue_hour, warranty_expiring_hour fields - Store times in UTC, clients convert to/from local timezone - Worker runs hourly, queries only users scheduled for that hour - Early exit optimization when no users need notifications - Admin UI displays custom notification times 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -4,9 +4,17 @@ import { useState } from 'react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { ColumnDef } from '@tanstack/react-table';
|
||||
import Link from 'next/link';
|
||||
import { MoreHorizontal, Trash2 } from 'lucide-react';
|
||||
import { MoreHorizontal, Trash2, Clock } from 'lucide-react';
|
||||
|
||||
import { notificationPrefsApi, type NotificationPreference } from '@/lib/api';
|
||||
|
||||
// Helper function to format UTC hour for display
|
||||
function formatHour(hour: number | null): string {
|
||||
if (hour === null) return '-';
|
||||
const h = hour % 12 || 12;
|
||||
const ampm = hour < 12 ? 'AM' : 'PM';
|
||||
return `${h}:00 ${ampm}`;
|
||||
}
|
||||
import { DataTable } from '@/components/data-table';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
@@ -166,6 +174,43 @@ export default function NotificationPrefsPage() {
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'notification_times',
|
||||
header: () => (
|
||||
<div className="flex items-center gap-1">
|
||||
<Clock className="h-4 w-4" />
|
||||
<span>Custom Times (UTC)</span>
|
||||
</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;
|
||||
|
||||
if (!hasCustomTimes) {
|
||||
return <span className="text-muted-foreground text-sm">Default</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-sm space-y-0.5">
|
||||
{task_due_soon_hour !== null && (
|
||||
<div className="text-xs">
|
||||
<span className="text-muted-foreground">Due Soon:</span> {formatHour(task_due_soon_hour)}
|
||||
</div>
|
||||
)}
|
||||
{task_overdue_hour !== null && (
|
||||
<div className="text-xs">
|
||||
<span className="text-muted-foreground">Overdue:</span> {formatHour(task_overdue_hour)}
|
||||
</div>
|
||||
)}
|
||||
{warranty_expiring_hour !== null && (
|
||||
<div className="text-xs">
|
||||
<span className="text-muted-foreground">Warranty:</span> {formatHour(warranty_expiring_hour)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
cell: ({ row }) => {
|
||||
|
||||
@@ -599,6 +599,10 @@ export interface NotificationPreference {
|
||||
warranty_expiring: 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;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user