fix(uat): batch — timeline overshoot, name-sync, reset-password, dashboard cleanup, queue/seed hygiene + alpha UAT findings doc
UAT findings landed across the last few Playwright + React Grab passes; single grouped commit so the index doesn't fragment into 30 one-liners. User & auth: - `user-settings`: name now updates the avatar + topbar menu after save (was reading stale session). - `me/password-reset`: 3 bugs (token validation, error response shape, redirect chain). - Admin user permission-overrides route honours the same envelope as the rest of the admin surface. Dashboard: - Removed obsolete `revenue-breakdown-chart` + `dashboard-widgets-card` (replaced by the customisable widget grid). - Strip `revenue_breakdown` from analytics route + use-analytics + service + integration test so nothing renders an empty card. - Activity log timeline overshoot fix (`interest-timeline` + `entity-activity-feed`). - Tightened tiles: active-deals, berth-heat-widget, pipeline-value, kpi-tile. - `dev-mode-banner`: derive dismissed state synchronously instead of via an effect (set-state-in-effect lint rule). Forms & lists (assorted polish): - client / company / yacht / interest / reminder forms — validation + empty-state copy + tab transitions. - companies/yachts list tweaks; berth recommender panel; qualification checklist; supplemental info request button. Infra & misc: - Queue workers (ai / email / notifications) — log shape + per-job timeout consistency. - Auth / brochures / users schema small adjustments; seeds reflect permissions matrix changes. - Scan shell + scanner manifest + AI admin page small fixes. - `next.config.transpilePackages` adds `echarts`/`zrender`/`echarts-for-react` (recommended config from echarts-for-react inside Next). Docs: - `docs/superpowers/audits/alpha-uat-master.md` — single rolling cross-cutting UAT findings doc (per CLAUDE.md convention). - `docs/BACKLOG.md`: dashboard stats cards (§I) + activity-log normalization (§J). - 2026-05-18 audit log updated with this batch. - `CLAUDE.md` — small manual UAT scaffold notes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -50,7 +50,6 @@ import { InterestEoiTab } from '@/components/interests/interest-eoi-tab';
|
||||
import { InterestContactLogTab } from '@/components/interests/interest-contact-log-tab';
|
||||
import { QualificationChecklist } from '@/components/interests/qualification-checklist';
|
||||
import { PaymentsSection } from '@/components/interests/payments-section';
|
||||
import { StageGuidanceCard } from '@/components/interests/stage-guidance-card';
|
||||
import { SkipAheadBanner } from '@/components/interests/skip-ahead-banner';
|
||||
import { InterestBerthStatusBanner } from '@/components/interests/interest-berth-status-banner';
|
||||
import { InterestContractTab } from '@/components/interests/interest-contract-tab';
|
||||
@@ -59,7 +58,12 @@ import { useConfirmation } from '@/hooks/use-confirmation';
|
||||
import { apiFetch } from '@/lib/api/client';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
type InterestPatchField = 'leadCategory' | 'source';
|
||||
type InterestPatchField =
|
||||
| 'leadCategory'
|
||||
| 'source'
|
||||
| 'desiredLengthFt'
|
||||
| 'desiredWidthFt'
|
||||
| 'desiredDraftFt';
|
||||
|
||||
const LEAD_CATEGORY_OPTIONS = LEAD_CATEGORIES.map((c) => ({
|
||||
value: c,
|
||||
@@ -381,8 +385,9 @@ function MilestoneSection({
|
||||
<Icon className={cn('size-4', isActive ? 'text-brand-600' : 'text-muted-foreground')} />
|
||||
<h3 className="text-sm font-semibold tracking-tight text-foreground">{title}</h3>
|
||||
{isActive ? (
|
||||
<span className="rounded-full bg-brand-100 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-brand-700">
|
||||
Next
|
||||
<span className="inline-flex items-center gap-1 rounded-full bg-brand-600 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-[0.08em] text-white shadow-sm">
|
||||
<span className="size-1.5 rounded-full bg-white/90" aria-hidden />
|
||||
Next step
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -844,15 +849,22 @@ function OverviewTab({
|
||||
depositExpectedAmount={interest.depositExpectedAmount ?? null}
|
||||
depositExpectedCurrency={interest.depositExpectedCurrency ?? null}
|
||||
/>
|
||||
) : (
|
||||
// §7.2: replace the empty Payments slot with a stage-aware
|
||||
// "next step" card on pre-reservation stages so the rep gets
|
||||
// an actionable prompt instead of dead space.
|
||||
<StageGuidanceCard
|
||||
stage={interest.pipelineStage as PipelineStage}
|
||||
hasLinkedBerth={(interest.linkedBerthCount ?? 0) > 0}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
{/* Pre-reservation: the dedicated "Next step" guidance card was
|
||||
removed in favour of a brighter NEXT STEP pill on the active
|
||||
MilestoneSection below (it already owns the workflow actions —
|
||||
two surfaces was redundant). Nurturing keeps a slim helper
|
||||
since no milestone is naturally "current" while a deal is
|
||||
paused. */}
|
||||
{interest.pipelineStage === 'nurturing' ? (
|
||||
<div className="rounded-xl border bg-card p-4 text-sm">
|
||||
<p className="font-medium text-foreground">Deal is on nurture</p>
|
||||
<p className="mt-1 text-xs text-muted-foreground">
|
||||
Schedule a follow-up reminder or log a contact when the prospect re-engages, then move
|
||||
them back to Qualified.
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* Sales-process milestones — phase-aware so the user only sees
|
||||
what's actionable now. Past milestones collapse into a tight
|
||||
@@ -1007,6 +1019,41 @@ function OverviewTab({
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
{/* Berth requirements — desired length / width / draft. Editable
|
||||
inline so reps can capture or correct a buyer's needs without
|
||||
leaving the Overview tab. These values drive the auto-tick on
|
||||
the "Dimensions confirmed" qualification row + the
|
||||
BerthRecommenderPanel rankings below. */}
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-sm font-medium mb-2">Berth requirements</h3>
|
||||
<dl>
|
||||
<EditableRow label="Desired length (ft)">
|
||||
<InlineEditableField
|
||||
value={interest.desiredLengthFt ?? null}
|
||||
onSave={save('desiredLengthFt')}
|
||||
placeholder="e.g. 60"
|
||||
emptyText="—"
|
||||
/>
|
||||
</EditableRow>
|
||||
<EditableRow label="Desired width (ft)">
|
||||
<InlineEditableField
|
||||
value={interest.desiredWidthFt ?? null}
|
||||
onSave={save('desiredWidthFt')}
|
||||
placeholder="e.g. 25"
|
||||
emptyText="—"
|
||||
/>
|
||||
</EditableRow>
|
||||
<EditableRow label="Desired draft (ft)">
|
||||
<InlineEditableField
|
||||
value={interest.desiredDraftFt ?? null}
|
||||
onSave={save('desiredDraftFt')}
|
||||
placeholder="e.g. 6"
|
||||
emptyText="—"
|
||||
/>
|
||||
</EditableRow>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
{/* Reminder */}
|
||||
{interest.reminderEnabled && (
|
||||
<div className="space-y-1">
|
||||
@@ -1102,6 +1149,7 @@ function OverviewTab({
|
||||
desiredWidthFt={toNum(interest.desiredWidthFt)}
|
||||
desiredDraftFt={toNum(interest.desiredDraftFt)}
|
||||
desiredUnit={interest.desiredLengthUnit === 'm' ? 'm' : 'ft'}
|
||||
linkedBerthCount={interest.linkedBerthCount ?? 0}
|
||||
/>
|
||||
{confirmDialog}
|
||||
{/* Mounted at the Overview level so the EOI milestone's "Generate EOI"
|
||||
@@ -1197,7 +1245,7 @@ export function getInterestTabs({
|
||||
},
|
||||
{
|
||||
id: 'recommendations',
|
||||
label: 'Recommendations',
|
||||
label: 'Berth Recommendations',
|
||||
content: (
|
||||
<BerthRecommenderPanel
|
||||
interestId={interestId}
|
||||
|
||||
Reference in New Issue
Block a user