Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
'use client';
|
|
|
|
|
|
2026-05-13 11:50:07 +02:00
|
|
|
import { useState } from 'react';
|
|
|
|
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
import { type ColumnDef } from '@tanstack/react-table';
|
2026-05-12 14:50:58 +02:00
|
|
|
import { Plus, CheckCircle2, Clock, Pencil, XCircle, AlertTriangle, Bell } from 'lucide-react';
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
import { formatDistanceToNow } from 'date-fns';
|
feat(mobile): mobile cards for reminders, audit log, users
Three new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender.
- ReminderCard: Bell icon, related-entity subtitle (User/Anchor/
FileText icon by entity type), due-date meta with
past-due flag, accent bar (rose=past-due,
amber=pending, slate=snoozed, emerald=done).
Snooze/Complete/Edit/Delete in actions menu.
- AuditLogCard: Action icon (Plus/Pencil/Trash2/Eye), entity
title, "{verb} by {actor}" subtitle, timestamp
meta, optional changed-field chip line. Accent
bar by action (created=emerald, updated=blue,
deleted=rose). Immutable, no actions menu.
- UserCard: Initials avatar, displayName/email, role meta
(Shield icon), last-login distance, "Inactive"
pill when deactivated. Accent bar (violet=
super_admin, slate=inactive, none=active).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:39:06 +02:00
|
|
|
import { useParams } from 'next/navigation';
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
|
|
|
|
|
import { DataTable } from '@/components/shared/data-table';
|
|
|
|
|
import { PageHeader } from '@/components/shared/page-header';
|
|
|
|
|
import { Button } from '@/components/ui/button';
|
|
|
|
|
import { Badge } from '@/components/ui/badge';
|
|
|
|
|
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
feat: round 2 — stage prompts, berth header, EOI inline edit, measurement units
Berth surfaces
- New compact mooring-chip header (colored plate + status pill, dock-label
in tooltip) replaces the redundant "Berth B1 / Sold / B DOCK" stack
- Berth list gains a "Latest deal stage" column showing the most-advanced
pipeline stage of any active linked interest (server-aggregated, ranks by
PIPELINE_STAGES index)
- "Linked prospect" Select on the status-change dialog rebuilt as a Command
combobox: search, recent-first sort, stage-coloured pills
Pipeline UX
- Reverting an interest to Open with linked berths now prompts: keep the
links, unlink and reset, or cancel. Silent when no berths are linked
- Activity feed + entity-activity feed normalise enum field values via
STAGE_LABELS / formatSource: "deposit_10pct → contract_sent" reads as
"10% Deposit → Contract Sent"
EOI generate dialog
- Inline-editable rows for client name, nationality (country combobox), and
yacht name — pencil affordance saves directly via clients/yachts PATCH
- Replaces the single "Edit on client's page" link with two contextual links
framed by short copy explaining what's inline vs what needs the canonical
page
- Backend EoiContext now includes client.id + yacht.id so the dialog can
PATCH without an extra round-trip
Company form
- New "Connections" section lets the rep attach members (clients) and yachts
during create. Yacht attach uses the existing transfer endpoint so audit
log + ownership history capture the change
- Inline "+ New client" / "+ New yacht" buttons open the canonical forms
stacked over the company sheet
- After save, the form chains to a yacht pull-in prompt (if any attached
client owns yachts not yet linked) and an optional "Create interest" step
pre-filled with the first attached client
Admin
- /admin landing gains a searchable index — typed query flattens groups into
a result list matching label + description + group title
- "Documenso & EOI" card relabelled to "EOI signing service" (consistent
with the user-facing language rename from round 1)
Measurement units (migration 0053)
- interests gains desired_*_m columns + desired_*_unit discriminators so
the rep's literal entry (ft OR m) is preserved verbatim instead of being
reconstructed from a single canonical column on every render
- yachts + berths gain matching *_unit columns alongside their existing
ft + m pairs; defaults to 'ft' so legacy rows still render normally
- Interest form POST/PATCH now sends both ft + m + unit; computed m is
derived from the ft canonical to keep the recommender SQL unchanged
Misc
- Active-deals tile + topbar type their Link href as `Route` instead of `any`
- Unused REPORT_TYPE_LABELS const dropped from generate-report-form
- Test fixtures (fill-eoi-form, documenso-payload, public-berths) updated
to include the new id + unit fields on the EoiContext / Berth shapes
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 15:28:22 +02:00
|
|
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
import {
|
|
|
|
|
Select,
|
|
|
|
|
SelectContent,
|
|
|
|
|
SelectItem,
|
|
|
|
|
SelectTrigger,
|
|
|
|
|
SelectValue,
|
|
|
|
|
} from '@/components/ui/select';
|
|
|
|
|
import { apiFetch } from '@/lib/api/client';
|
2026-05-12 14:50:58 +02:00
|
|
|
import { useCreateFromUrl } from '@/hooks/use-create-from-url';
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
import { usePermissions } from '@/hooks/use-permissions';
|
feat(mobile): mobile cards for reminders, audit log, users
Three new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender.
- ReminderCard: Bell icon, related-entity subtitle (User/Anchor/
FileText icon by entity type), due-date meta with
past-due flag, accent bar (rose=past-due,
amber=pending, slate=snoozed, emerald=done).
Snooze/Complete/Edit/Delete in actions menu.
- AuditLogCard: Action icon (Plus/Pencil/Trash2/Eye), entity
title, "{verb} by {actor}" subtitle, timestamp
meta, optional changed-field chip line. Accent
bar by action (created=emerald, updated=blue,
deleted=rose). Immutable, no actions menu.
- UserCard: Initials avatar, displayName/email, role meta
(Shield icon), last-login distance, "Inactive"
pill when deactivated. Accent bar (violet=
super_admin, slate=inactive, none=active).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:39:06 +02:00
|
|
|
import { ReminderCard } from './reminder-card';
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
import { ReminderForm } from './reminder-form';
|
|
|
|
|
import { SnoozeDialog } from './snooze-dialog';
|
|
|
|
|
|
|
|
|
|
interface Reminder {
|
|
|
|
|
id: string;
|
|
|
|
|
title: string;
|
|
|
|
|
note: string | null;
|
|
|
|
|
dueAt: string;
|
|
|
|
|
priority: 'low' | 'medium' | 'high' | 'urgent';
|
|
|
|
|
status: 'pending' | 'snoozed' | 'completed' | 'dismissed';
|
|
|
|
|
assignedTo: string | null;
|
|
|
|
|
createdBy: string;
|
|
|
|
|
clientId: string | null;
|
|
|
|
|
interestId: string | null;
|
|
|
|
|
berthId: string | null;
|
|
|
|
|
autoGenerated: boolean;
|
|
|
|
|
snoozedUntil: string | null;
|
|
|
|
|
completedAt: string | null;
|
|
|
|
|
createdAt: string;
|
|
|
|
|
client?: { id: string; fullName: string } | null;
|
|
|
|
|
interest?: { id: string; pipelineStage: string } | null;
|
|
|
|
|
berth?: { id: string; mooringNumber: string } | null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const PRIORITY_CONFIG = {
|
|
|
|
|
urgent: { label: 'Urgent', className: 'bg-red-600 text-white' },
|
|
|
|
|
high: { label: 'High', className: 'bg-orange-500 text-white' },
|
|
|
|
|
medium: { label: 'Medium', className: 'bg-blue-500 text-white' },
|
|
|
|
|
low: { label: 'Low', className: 'bg-gray-400 text-white' },
|
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
const STATUS_CONFIG = {
|
|
|
|
|
pending: { label: 'Pending', icon: Bell },
|
|
|
|
|
snoozed: { label: 'Snoozed', icon: Clock },
|
|
|
|
|
completed: { label: 'Completed', icon: CheckCircle2 },
|
|
|
|
|
dismissed: { label: 'Dismissed', icon: XCircle },
|
|
|
|
|
} as const;
|
|
|
|
|
|
2026-05-12 14:50:58 +02:00
|
|
|
interface ReminderListProps {
|
|
|
|
|
/**
|
|
|
|
|
* Embedded mode (used by the Inbox page) drops the PageHeader and
|
|
|
|
|
* surfaces the "New Reminder" button inline so the section can render
|
|
|
|
|
* alongside the Alerts section without duplicating page chrome.
|
|
|
|
|
*/
|
|
|
|
|
embedded?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function ReminderList({ embedded = false }: ReminderListProps = {}) {
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
const [formOpen, setFormOpen] = useState(false);
|
2026-05-12 14:50:58 +02:00
|
|
|
useCreateFromUrl(() => setFormOpen(true));
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
const [editingReminder, setEditingReminder] = useState<Reminder | null>(null);
|
|
|
|
|
const [snoozingId, setSnoozingId] = useState<string | null>(null);
|
|
|
|
|
const [viewMode, setViewMode] = useState<'my' | 'all'>('my');
|
|
|
|
|
const [statusFilter, setStatusFilter] = useState<string>('active');
|
|
|
|
|
const [priorityFilter, setPriorityFilter] = useState<string>('all');
|
|
|
|
|
const { can } = usePermissions();
|
|
|
|
|
const canViewAll = can('reminders', 'view_all');
|
feat(mobile): mobile cards for reminders, audit log, users
Three new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender.
- ReminderCard: Bell icon, related-entity subtitle (User/Anchor/
FileText icon by entity type), due-date meta with
past-due flag, accent bar (rose=past-due,
amber=pending, slate=snoozed, emerald=done).
Snooze/Complete/Edit/Delete in actions menu.
- AuditLogCard: Action icon (Plus/Pencil/Trash2/Eye), entity
title, "{verb} by {actor}" subtitle, timestamp
meta, optional changed-field chip line. Accent
bar by action (created=emerald, updated=blue,
deleted=rose). Immutable, no actions menu.
- UserCard: Initials avatar, displayName/email, role meta
(Shield icon), last-login distance, "Inactive"
pill when deactivated. Accent bar (violet=
super_admin, slate=inactive, none=active).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:39:06 +02:00
|
|
|
const params = useParams<{ portSlug: string }>();
|
|
|
|
|
const portSlug = params?.portSlug ?? '';
|
2026-05-13 11:50:07 +02:00
|
|
|
const queryClient = useQueryClient();
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
|
2026-05-13 11:50:07 +02:00
|
|
|
// useQuery replaces the prior useEffect(fetch+setState) pattern.
|
|
|
|
|
// The query key captures every filter so a switch refetches; the
|
|
|
|
|
// mutation handlers below invalidate-by-prefix to refresh after
|
|
|
|
|
// complete/dismiss.
|
|
|
|
|
const remindersQuery = useQuery<{ reminders: Reminder[]; total: number }>({
|
|
|
|
|
queryKey: ['reminders', viewMode, statusFilter, priorityFilter],
|
|
|
|
|
queryFn: async () => {
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
if (viewMode === 'my') {
|
|
|
|
|
const res = await apiFetch<{ data: Reminder[] }>('/api/v1/reminders/my');
|
2026-05-13 11:50:07 +02:00
|
|
|
const filtered =
|
|
|
|
|
priorityFilter === 'all'
|
|
|
|
|
? res.data
|
|
|
|
|
: res.data.filter((r) => r.priority === priorityFilter);
|
|
|
|
|
return { reminders: filtered, total: filtered.length };
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
}
|
2026-05-13 11:50:07 +02:00
|
|
|
const sp = new URLSearchParams({ limit: '50', order: 'asc', sort: 'dueAt' });
|
|
|
|
|
if (statusFilter === 'active') sp.set('status', 'pending');
|
|
|
|
|
else if (statusFilter !== 'all') sp.set('status', statusFilter);
|
|
|
|
|
if (priorityFilter !== 'all') sp.set('priority', priorityFilter);
|
|
|
|
|
const res = await apiFetch<{
|
|
|
|
|
data: Reminder[];
|
|
|
|
|
pagination: { total: number };
|
|
|
|
|
}>(`/api/v1/reminders?${sp}`);
|
|
|
|
|
return { reminders: res.data, total: res.pagination.total };
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
const reminders = remindersQuery.data?.reminders ?? [];
|
|
|
|
|
const total = remindersQuery.data?.total ?? 0;
|
|
|
|
|
const loading = remindersQuery.isLoading;
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
|
|
|
|
|
async function handleComplete(id: string) {
|
|
|
|
|
await apiFetch(`/api/v1/reminders/${id}/complete`, { method: 'POST' });
|
2026-05-13 11:50:07 +02:00
|
|
|
void queryClient.invalidateQueries({ queryKey: ['reminders'] });
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function handleDismiss(id: string) {
|
|
|
|
|
await apiFetch(`/api/v1/reminders/${id}/dismiss`, { method: 'POST' });
|
2026-05-13 11:50:07 +02:00
|
|
|
void queryClient.invalidateQueries({ queryKey: ['reminders'] });
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isOverdue(dueAt: string, status: string): boolean {
|
|
|
|
|
return (status === 'pending' || status === 'snoozed') && new Date(dueAt) < new Date();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const columns: ColumnDef<Reminder, unknown>[] = [
|
|
|
|
|
{
|
|
|
|
|
accessorKey: 'priority',
|
|
|
|
|
header: '',
|
|
|
|
|
cell: ({ row }) => {
|
|
|
|
|
const config = PRIORITY_CONFIG[row.original.priority];
|
|
|
|
|
return <Badge className={`${config.className} text-[10px] px-1.5`}>{config.label}</Badge>;
|
|
|
|
|
},
|
|
|
|
|
size: 70,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
accessorKey: 'title',
|
|
|
|
|
header: 'Reminder',
|
|
|
|
|
cell: ({ row }) => {
|
|
|
|
|
const overdue = isOverdue(row.original.dueAt, row.original.status);
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex flex-col gap-0.5">
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<span className="font-medium">{row.original.title}</span>
|
|
|
|
|
{row.original.autoGenerated && (
|
|
|
|
|
<Badge variant="outline" className="text-[10px]">
|
|
|
|
|
Auto
|
|
|
|
|
</Badge>
|
|
|
|
|
)}
|
|
|
|
|
{overdue && <AlertTriangle className="h-3.5 w-3.5 text-destructive" />}
|
|
|
|
|
</div>
|
|
|
|
|
{row.original.client && (
|
|
|
|
|
<span className="text-xs text-muted-foreground">
|
|
|
|
|
Client: {row.original.client.fullName}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
{row.original.berth && (
|
|
|
|
|
<span className="text-xs text-muted-foreground">
|
|
|
|
|
Berth: {row.original.berth.mooringNumber}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
accessorKey: 'dueAt',
|
|
|
|
|
header: 'Due',
|
|
|
|
|
cell: ({ row }) => {
|
|
|
|
|
const overdue = isOverdue(row.original.dueAt, row.original.status);
|
|
|
|
|
const date = new Date(row.original.dueAt);
|
|
|
|
|
return (
|
|
|
|
|
<div className={overdue ? 'text-destructive font-medium' : ''}>
|
|
|
|
|
<div className="text-sm">{date.toLocaleDateString()}</div>
|
|
|
|
|
<div className="text-xs text-muted-foreground">
|
|
|
|
|
{formatDistanceToNow(date, { addSuffix: true })}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
accessorKey: 'status',
|
|
|
|
|
header: 'Status',
|
|
|
|
|
cell: ({ row }) => {
|
|
|
|
|
const config = STATUS_CONFIG[row.original.status];
|
|
|
|
|
const Icon = config.icon;
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex items-center gap-1.5 text-sm">
|
|
|
|
|
<Icon className="h-3.5 w-3.5" />
|
|
|
|
|
{config.label}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'actions',
|
|
|
|
|
header: '',
|
|
|
|
|
cell: ({ row }) => {
|
|
|
|
|
if (row.original.status === 'completed' || row.original.status === 'dismissed') {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return (
|
2026-05-12 14:50:58 +02:00
|
|
|
<TooltipProvider delayDuration={150}>
|
|
|
|
|
<div className="flex items-center justify-end gap-1">
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="sm"
|
|
|
|
|
aria-label="Mark complete"
|
|
|
|
|
className="text-green-600 hover:text-green-700"
|
|
|
|
|
onClick={() => handleComplete(row.original.id)}
|
|
|
|
|
>
|
|
|
|
|
<CheckCircle2 className="h-4 w-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>Mark complete</TooltipContent>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="sm"
|
|
|
|
|
aria-label="Snooze"
|
|
|
|
|
onClick={() => setSnoozingId(row.original.id)}
|
|
|
|
|
>
|
|
|
|
|
<Clock className="h-4 w-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>Snooze</TooltipContent>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="sm"
|
|
|
|
|
aria-label="Edit reminder"
|
|
|
|
|
className="text-muted-foreground hover:text-foreground"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
setEditingReminder(row.original);
|
|
|
|
|
setFormOpen(true);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Pencil className="h-4 w-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>Edit</TooltipContent>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
<Tooltip>
|
|
|
|
|
<TooltipTrigger asChild>
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="sm"
|
|
|
|
|
aria-label="Dismiss"
|
|
|
|
|
className="text-muted-foreground hover:text-foreground"
|
|
|
|
|
onClick={() => handleDismiss(row.original.id)}
|
|
|
|
|
>
|
|
|
|
|
<XCircle className="h-4 w-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
</TooltipTrigger>
|
|
|
|
|
<TooltipContent>Dismiss</TooltipContent>
|
|
|
|
|
</Tooltip>
|
|
|
|
|
</div>
|
|
|
|
|
</TooltipProvider>
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
enableSorting: false,
|
2026-05-12 14:50:58 +02:00
|
|
|
size: 160,
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div>
|
2026-05-12 14:50:58 +02:00
|
|
|
{!embedded ? (
|
|
|
|
|
<PageHeader
|
|
|
|
|
title="Reminders"
|
|
|
|
|
description={`${total} reminder${total !== 1 ? 's' : ''}`}
|
|
|
|
|
actions={
|
|
|
|
|
<Button
|
|
|
|
|
onClick={() => {
|
|
|
|
|
setEditingReminder(null);
|
|
|
|
|
setFormOpen(true);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Plus className="mr-1.5 h-4 w-4" />
|
|
|
|
|
New Reminder
|
|
|
|
|
</Button>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="mb-3 flex justify-end">
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
<Button
|
2026-05-12 14:50:58 +02:00
|
|
|
size="sm"
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
onClick={() => {
|
|
|
|
|
setEditingReminder(null);
|
|
|
|
|
setFormOpen(true);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<Plus className="mr-1.5 h-4 w-4" />
|
|
|
|
|
New Reminder
|
|
|
|
|
</Button>
|
2026-05-12 14:50:58 +02:00
|
|
|
</div>
|
|
|
|
|
)}
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
|
2026-05-03 17:09:27 +02:00
|
|
|
{/* Wrap on phone widths so the priority filter doesn't get pushed
|
|
|
|
|
off-screen by the My/All tabs + status filter taking the full row. */}
|
|
|
|
|
<div className="flex flex-wrap items-center gap-3 mb-4 sm:gap-4">
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
{canViewAll && (
|
|
|
|
|
<Tabs value={viewMode} onValueChange={(v) => setViewMode(v as 'my' | 'all')}>
|
|
|
|
|
<TabsList>
|
|
|
|
|
<TabsTrigger value="my">My Reminders</TabsTrigger>
|
|
|
|
|
<TabsTrigger value="all">All Reminders</TabsTrigger>
|
|
|
|
|
</TabsList>
|
|
|
|
|
</Tabs>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{viewMode === 'all' && (
|
|
|
|
|
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
|
|
|
|
<SelectTrigger className="w-32">
|
|
|
|
|
<SelectValue />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
<SelectItem value="active">Active</SelectItem>
|
|
|
|
|
<SelectItem value="pending">Pending</SelectItem>
|
|
|
|
|
<SelectItem value="snoozed">Snoozed</SelectItem>
|
|
|
|
|
<SelectItem value="completed">Completed</SelectItem>
|
|
|
|
|
<SelectItem value="dismissed">Dismissed</SelectItem>
|
|
|
|
|
<SelectItem value="all">All</SelectItem>
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<Select value={priorityFilter} onValueChange={setPriorityFilter}>
|
|
|
|
|
<SelectTrigger className="w-32">
|
|
|
|
|
<SelectValue />
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
<SelectContent>
|
|
|
|
|
<SelectItem value="all">All Priorities</SelectItem>
|
|
|
|
|
<SelectItem value="urgent">Urgent</SelectItem>
|
|
|
|
|
<SelectItem value="high">High</SelectItem>
|
|
|
|
|
<SelectItem value="medium">Medium</SelectItem>
|
|
|
|
|
<SelectItem value="low">Low</SelectItem>
|
|
|
|
|
</SelectContent>
|
|
|
|
|
</Select>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<DataTable
|
|
|
|
|
columns={columns}
|
|
|
|
|
data={reminders}
|
|
|
|
|
isLoading={loading}
|
|
|
|
|
getRowId={(row) => row.id}
|
feat(mobile): mobile cards for reminders, audit log, users
Three new <EntityCard> files using the shared <ListCard> shell, wired
into each list page's <DataTable> via cardRender.
- ReminderCard: Bell icon, related-entity subtitle (User/Anchor/
FileText icon by entity type), due-date meta with
past-due flag, accent bar (rose=past-due,
amber=pending, slate=snoozed, emerald=done).
Snooze/Complete/Edit/Delete in actions menu.
- AuditLogCard: Action icon (Plus/Pencil/Trash2/Eye), entity
title, "{verb} by {actor}" subtitle, timestamp
meta, optional changed-field chip line. Accent
bar by action (created=emerald, updated=blue,
deleted=rose). Immutable, no actions menu.
- UserCard: Initials avatar, displayName/email, role meta
(Shield icon), last-login distance, "Inactive"
pill when deactivated. Accent bar (violet=
super_admin, slate=inactive, none=active).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:39:06 +02:00
|
|
|
cardRender={(row) => (
|
|
|
|
|
<ReminderCard
|
|
|
|
|
reminder={row.original}
|
|
|
|
|
portSlug={portSlug}
|
|
|
|
|
onComplete={handleComplete}
|
|
|
|
|
onSnooze={setSnoozingId}
|
|
|
|
|
onDismiss={handleDismiss}
|
|
|
|
|
onEdit={(r) => {
|
|
|
|
|
setEditingReminder(r);
|
|
|
|
|
setFormOpen(true);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
emptyState={
|
|
|
|
|
<div className="text-center py-8">
|
|
|
|
|
<Bell className="mx-auto h-8 w-8 text-muted-foreground mb-2" />
|
|
|
|
|
<p className="text-muted-foreground">No reminders.</p>
|
|
|
|
|
<Button
|
|
|
|
|
variant="link"
|
|
|
|
|
onClick={() => {
|
|
|
|
|
setEditingReminder(null);
|
|
|
|
|
setFormOpen(true);
|
|
|
|
|
}}
|
|
|
|
|
className="mt-2"
|
|
|
|
|
>
|
|
|
|
|
Create your first reminder
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<ReminderForm
|
|
|
|
|
open={formOpen}
|
|
|
|
|
onOpenChange={setFormOpen}
|
|
|
|
|
reminder={editingReminder}
|
2026-05-13 11:50:07 +02:00
|
|
|
onSuccess={() => queryClient.invalidateQueries({ queryKey: ['reminders'] })}
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<SnoozeDialog
|
|
|
|
|
open={!!snoozingId}
|
|
|
|
|
onOpenChange={(open) => {
|
|
|
|
|
if (!open) setSnoozingId(null);
|
|
|
|
|
}}
|
|
|
|
|
reminderId={snoozingId}
|
2026-05-13 11:50:07 +02:00
|
|
|
onSuccess={() => queryClient.invalidateQueries({ queryKey: ['reminders'] })}
|
Implement reminders system with full CRUD and background processors
- Reminders service: create, update, delete, complete, snooze, dismiss
- List with filters (status, priority, assignee, entity, date range)
- My/overdue/upcoming convenience endpoints
- BullMQ processors: auto-follow-up creation (BR-060) and overdue notifications
- Snooze with presets (1h, 4h, tomorrow, next week) and custom datetime
- Un-snooze logic: snoozed reminders auto-revert to pending when snooze expires
- UI: filterable list with my/all toggle, priority badges, overdue indicators
- Permission-gated: view_own, view_all, create, assign_others
- Entity linking: reminders can link to clients, interests, or berths
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 16:27:34 -04:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|