fix(uat): dashboard snapshots current-state, pulse-chip gate, phone display, chip width
- pipeline funnel: count active interests by current stage (drop created_at window) — backfill had collapsed it to early stages (UAT 2026-06-03) - pipeline value tile: render current-state (don't thread the date range) - deal pulse chip: gate on the pulse_enabled master toggle (default ON) — was rendering even when admin turned it off; useFeatureFlag gains a default arg + the feature-flag endpoint a ?default= param (default-ON safe) - contact phone display: show international format + country flag (E164), not the bare national format that hid the country - berths: remove the dead row-density toggle; widen "Under offer to" chip on desktop so client names aren't truncated Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,16 +4,7 @@ import { useEffect, useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { useRouter, useParams } from 'next/navigation';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
Anchor,
|
||||
Archive,
|
||||
CircleDollarSign,
|
||||
Plus,
|
||||
Rows3,
|
||||
Rows4,
|
||||
Tag as TagIcon,
|
||||
TagsIcon,
|
||||
} from 'lucide-react';
|
||||
import { Anchor, Archive, CircleDollarSign, Plus, Tag as TagIcon, TagsIcon } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { apiFetch } from '@/lib/api/client';
|
||||
@@ -103,8 +94,10 @@ export function BerthList() {
|
||||
// Persisted column visibility + row density + dimension unit - same
|
||||
// pattern as ClientList / InterestList; density falls back to
|
||||
// 'comfortable' and dimensionUnit to 'ft' for users who haven't picked.
|
||||
const { hidden, setHidden, density, setDensity, dimensionUnit, setDimensionUnit } =
|
||||
useTablePreferences('berths', BERTH_DEFAULT_HIDDEN);
|
||||
const { hidden, setHidden, dimensionUnit, setDimensionUnit } = useTablePreferences(
|
||||
'berths',
|
||||
BERTH_DEFAULT_HIDDEN,
|
||||
);
|
||||
const columnVisibility = Object.fromEntries(hidden.map((id) => [id, false]));
|
||||
const berthColumns = getBerthColumns(dimensionUnit);
|
||||
|
||||
@@ -187,29 +180,11 @@ export function BerthList() {
|
||||
applyView({ filters: savedFilters, sort: savedSort });
|
||||
}}
|
||||
/>
|
||||
{/* Table-only controls — hidden in card mode (<lg, matching
|
||||
DataTable's table/card switch). The BerthCard ignores row
|
||||
density + dimension unit and renders no column set, so these
|
||||
toggles have no visible effect there and read as broken. */}
|
||||
{/* Table-only controls — hidden in card mode (<md, matching
|
||||
DataTable's table/card switch). The BerthCard ignores the
|
||||
dimension unit + renders no column set, so these toggles have
|
||||
no visible effect there. */}
|
||||
<div className="hidden items-center gap-2 md:flex">
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => setDensity(density === 'compact' ? 'comfortable' : 'compact')}
|
||||
aria-label={
|
||||
density === 'compact'
|
||||
? 'Switch to comfortable row spacing'
|
||||
: 'Switch to compact row spacing'
|
||||
}
|
||||
title={density === 'compact' ? 'Comfortable rows' : 'Compact rows'}
|
||||
>
|
||||
{density === 'compact' ? (
|
||||
<Rows3 className="h-4 w-4" aria-hidden />
|
||||
) : (
|
||||
<Rows4 className="h-4 w-4" aria-hidden />
|
||||
)}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
@@ -238,7 +213,6 @@ export function BerthList() {
|
||||
<DataTable<BerthRow>
|
||||
columns={berthColumns}
|
||||
columnVisibility={columnVisibility}
|
||||
density={density}
|
||||
data={data}
|
||||
isLoading={isLoading}
|
||||
pagination={{
|
||||
|
||||
@@ -86,7 +86,9 @@ export function BerthOccupancyChip({
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className={cn(
|
||||
'inline-flex items-center gap-1.5 rounded-md border border-amber-300 bg-amber-50 px-2 py-0.5 text-xs text-amber-900 hover:bg-amber-100 transition-colors',
|
||||
compact && 'max-w-[200px]',
|
||||
// Cap tight on narrow viewports, but give the name room on desktop
|
||||
// so it isn't truncated to "Philippe Ca…" (UAT 2026-06-03).
|
||||
compact && 'max-w-[200px] md:max-w-[460px]',
|
||||
)}
|
||||
title={`Open ${primary.clientName} (${stageLabel(primary.pipelineStage)})`}
|
||||
>
|
||||
|
||||
@@ -164,7 +164,10 @@ export const DASHBOARD_WIDGETS: readonly DashboardWidget[] = [
|
||||
label: 'Pipeline Value',
|
||||
description:
|
||||
'Gross + weighted forecast, broken down by pipeline stage so leadership can see what is near-close vs speculative.',
|
||||
render: (range) => <PipelineValueTile range={range} />,
|
||||
// Current-state snapshot: pipeline value = sum across ALL active deals,
|
||||
// not "added in the selected window". Don't thread the range (UAT
|
||||
// 2026-06-03 — windowing it dropped older deals + confused the headline).
|
||||
render: () => <PipelineValueTile />,
|
||||
// Lives in the chart grid (not the narrow rail) so the per-stage
|
||||
// breakdown rows have room to breathe alongside the headline numbers,
|
||||
// and the rail stays reserved for reminders / alerts / glance tiles.
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Activity, ExternalLink } from 'lucide-react';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { computeDealHealth, type DealHealthInput } from '@/lib/services/deal-health';
|
||||
import { useFeatureFlag } from '@/hooks/use-feature-flag';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const PULSE_TINT: Record<'cold' | 'warm' | 'hot', string> = {
|
||||
@@ -31,9 +32,13 @@ const PULSE_LABEL: Record<'cold' | 'warm' | 'hot', string> = {
|
||||
*/
|
||||
export function DealPulseChip({ interest }: { interest: DealHealthInput }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
// Master toggle: Admin → Pulse → "Show deal pulse chips" (pulse_enabled).
|
||||
// Defaults ON (chip visible) when the port hasn't set it; hidden only when
|
||||
// explicitly disabled.
|
||||
const pulseEnabled = useFeatureFlag('pulse_enabled', true);
|
||||
|
||||
// Closed / archived deals don't get a pulse - UX would be confusing.
|
||||
if (interest.archivedAt || interest.outcome) return null;
|
||||
// Hidden when the port disabled pulse chips, or for closed/archived deals.
|
||||
if (!pulseEnabled || interest.archivedAt || interest.outcome) return null;
|
||||
|
||||
const health = computeDealHealth(interest);
|
||||
const tint = PULSE_TINT[health.pulse];
|
||||
|
||||
@@ -7,6 +7,7 @@ import { PhoneInput, type PhoneInputValue } from '@/components/shared/phone-inpu
|
||||
import { toastError } from '@/lib/api/toast-error';
|
||||
import { parsePhone } from '@/lib/i18n/phone';
|
||||
import type { CountryCode } from '@/lib/i18n/countries';
|
||||
import { CountryFlag } from '@/components/shared/country-flag';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface InlinePhoneFieldProps {
|
||||
@@ -122,11 +123,16 @@ export function InlinePhoneField({
|
||||
);
|
||||
}
|
||||
|
||||
// Display: prefer the parsed national format (more readable than raw E.164).
|
||||
// Display: international format so the dialing/country code is visible
|
||||
// ('+590 690 58 59 18'), paired with a country flag for quick scanning.
|
||||
// The bare national format ('0690 58 59 18') hid which country the number
|
||||
// belonged to (UAT 2026-06-03).
|
||||
let display: string | null = null;
|
||||
let flagCountry: string | null = (country as string | null) ?? null;
|
||||
if (e164) {
|
||||
const parsed = parsePhone(e164, (country as CountryCode | undefined) ?? defaultCountry);
|
||||
display = parsed.national ?? e164;
|
||||
display = parsed.international ?? parsed.national ?? e164;
|
||||
flagCountry = parsed.country ?? flagCountry;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -142,6 +148,9 @@ export function InlinePhoneField({
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{display && flagCountry ? (
|
||||
<CountryFlag code={flagCountry} className="h-2.5 w-3.5 shrink-0" decorative />
|
||||
) : null}
|
||||
<span className={cn('flex-1', !display && 'text-muted-foreground')}>
|
||||
{display ?? emptyText}
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user