From 203f543e602bfc6634d421e9947557c8bfe0908a Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 21 May 2026 17:28:20 +0200 Subject: [PATCH] =?UTF-8?q?feat(uat-batch-5):=20UI=20polish=20=E2=80=94=20?= =?UTF-8?q?dialog=20width,=20chart=20centering,=20recommender=20pill,=20au?= =?UTF-8?q?dit=20link,=20inbox=20reorder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six surgical Wave-2-3 wins: - UploadForSigningDialog: dialog widened to max-w-[1400px] w-[95vw] so the place-fields step actually has room; recipient row converts from fixed grid to flex (name flex-1, email flex-[2] for the longer string, role w-40, delete shrink-0); invitation-message textarea rows 3 → 6. - ChartCard becomes flex-col with flex-1 + items-center on CardContent so charts vertically center when neighbouring cards make the row taller (e.g. Pipeline Value's full breakdown). - Berth recommender pill: drops the "Tier {letter} · " prefix; shows just the plain-English label ("Open" / "Fall-through" / "Active interest" / "Late stage") as a Popover trigger that explains the 4-state ladder. HelpCircle icon makes the tooltip discoverable. - Activity feed gains a "See all" link in the header pointing at //admin/audit, permission-gated by `admin.view_audit_log`. - Inbox section order swaps to Reminders above Alerts (rep-noted priority); PageHeader title flips to "Reminders & Alerts". Section ids, deep-link hashes, and localStorage open-state keys untouched. - Inbox ReminderList (embedded mode only): "New Reminder" button now shares the filter row (right-aligned via ml-auto) instead of occupying its own dedicated row above the filters. tsc clean. 1419/1419 vitest pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/dashboard/activity-feed.tsx | 19 ++++++- src/components/dashboard/chart-card.tsx | 8 ++- .../documents/upload-for-signing-dialog.tsx | 18 +++--- src/components/inbox/inbox-page-shell.tsx | 32 +++++------ .../interests/berth-recommender-panel.tsx | 56 ++++++++++++++++--- src/components/reminders/reminder-list.tsx | 33 ++++++----- 6 files changed, 113 insertions(+), 53 deletions(-) diff --git a/src/components/dashboard/activity-feed.tsx b/src/components/dashboard/activity-feed.tsx index d5853456..0bdc62eb 100644 --- a/src/components/dashboard/activity-feed.tsx +++ b/src/components/dashboard/activity-feed.tsx @@ -2,11 +2,14 @@ import { useQuery } from '@tanstack/react-query'; import { formatDistanceToNow } from 'date-fns'; +import Link from 'next/link'; +import { useParams } from 'next/navigation'; import { apiFetch } from '@/lib/api/client'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { CardSkeleton } from '@/components/shared/loading-skeleton'; +import { usePermissions } from '@/hooks/use-permissions'; import { WidgetErrorBoundary } from './widget-error-boundary'; import { STAGE_LABELS, @@ -171,6 +174,11 @@ function ActionBadge({ action }: { action: string }) { } function ActivityFeedInner() { + const params = useParams<{ portSlug: string }>(); + const portSlug = params?.portSlug ?? ''; + const { can } = usePermissions(); + const canViewAuditLog = can('admin', 'view_audit_log'); + const { data, isLoading } = useQuery({ queryKey: ['dashboard', 'activity'], queryFn: () => apiFetch('/api/v1/dashboard/activity'), @@ -190,8 +198,17 @@ function ActivityFeedInner() { return ( - + Recent Activity + {canViewAuditLog && portSlug ? ( + + See all + + ) : null} {items.length === 0 ? ( diff --git a/src/components/dashboard/chart-card.tsx b/src/components/dashboard/chart-card.tsx index c2b5f934..ba690814 100644 --- a/src/components/dashboard/chart-card.tsx +++ b/src/components/dashboard/chart-card.tsx @@ -84,7 +84,7 @@ export function ChartCard({ } return ( - +
{title} @@ -116,8 +116,10 @@ export function ChartCard({ - -
{children}
+ +
+ {children} +
); diff --git a/src/components/documents/upload-for-signing-dialog.tsx b/src/components/documents/upload-for-signing-dialog.tsx index 428e27a5..b14731ad 100644 --- a/src/components/documents/upload-for-signing-dialog.tsx +++ b/src/components/documents/upload-for-signing-dialog.tsx @@ -163,7 +163,7 @@ export function UploadForSigningDialog({ if (!open) return null; return ( - + Recipients (in signing order)
{recipients.map((r, i) => ( -
- +
+ #{r.signingOrder} update(i, { name: e.target.value })} /> update(i, { role: v as Recipient['role'] })} > - + @@ -670,7 +670,7 @@ function RecipientsStep({ size="icon" onClick={() => remove(i)} aria-label="Remove recipient" - className="col-span-1" + className="shrink-0" > @@ -689,8 +689,8 @@ function RecipientsStep({ id="invitation-message" value={invitationMessage} onChange={(e) => onInvitationMessageChange(e.target.value)} - placeholder="Hi John — please review the attached contract before signing. Reach out if anything needs adjusting." - rows={3} + placeholder="Hi John, please review the attached contract before signing. Reach out if anything needs adjusting." + rows={6} maxLength={1000} className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm shadow-xs focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring resize-none" /> diff --git a/src/components/inbox/inbox-page-shell.tsx b/src/components/inbox/inbox-page-shell.tsx index 8fb9325b..14ee3895 100644 --- a/src/components/inbox/inbox-page-shell.tsx +++ b/src/components/inbox/inbox-page-shell.tsx @@ -75,12 +75,26 @@ export function InboxPageShell() { return (
+
+ } + label="Reminders" + open={remindersOpen} + onToggle={toggleReminders} + /> + {remindersOpen ? ( +
+ +
+ ) : null} +
+
} @@ -95,20 +109,6 @@ export function InboxPageShell() {
) : null} - -
- } - label="Reminders" - open={remindersOpen} - onToggle={toggleReminders} - /> - {remindersOpen ? ( -
- -
- ) : null} -
); } diff --git a/src/components/interests/berth-recommender-panel.tsx b/src/components/interests/berth-recommender-panel.tsx index 78d2344b..86d9aced 100644 --- a/src/components/interests/berth-recommender-panel.tsx +++ b/src/components/interests/berth-recommender-panel.tsx @@ -4,12 +4,22 @@ import { useState, useMemo } from 'react'; import Link from 'next/link'; import { useParams } from 'next/navigation'; import { useQuery } from '@tanstack/react-query'; -import { ChevronDown, ChevronUp, Filter, Flame, Plus, RefreshCw, Sparkles } from 'lucide-react'; +import { + ChevronDown, + ChevronUp, + Filter, + Flame, + HelpCircle, + Plus, + RefreshCw, + Sparkles, +} from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; import { Select, SelectContent, @@ -172,14 +182,42 @@ function RecommendationCard({ rec, portSlug, onAdd }: RecommendationCardProps) { {rec.mooringNumber} {rec.area ? {rec.area} : null} {formatStatus(rec.status)} - - Tier {rec.tier} · {tier.label} - + + + + + +

Recommender state

+
    +
  • + Open: never had an + interest, ready for new prospects. +
  • +
  • + Fall-through: a prior + interest didn't close; warm and worth pitching again. +
  • +
  • + Active interest: another deal + is in play. Coordinate before pitching. +
  • +
  • + Late stage: another deal is + near-sold; treat as backup only. +
  • +
+
+
{showHeat ? ( diff --git a/src/components/reminders/reminder-list.tsx b/src/components/reminders/reminder-list.tsx index 86062f2f..36fb9829 100644 --- a/src/components/reminders/reminder-list.tsx +++ b/src/components/reminders/reminder-list.tsx @@ -295,23 +295,12 @@ export function ReminderList({ embedded = false }: ReminderListProps = {}) { } /> - ) : ( -
- -
- )} + ) : null} {/* 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. */} + off-screen by the My/All tabs + status filter taking the full row. + In embedded mode, the "New Reminder" button shares this row + (right-aligned via ml-auto) so filters + CTA stay visually tight. */}
{canViewAll && ( setViewMode(v as 'my' | 'all')}> @@ -350,6 +339,20 @@ export function ReminderList({ embedded = false }: ReminderListProps = {}) { Low + + {embedded ? ( + + ) : null}