diff --git a/src/components/admin/users/user-card.tsx b/src/components/admin/users/user-card.tsx
index 74f82bc4..4f78b875 100644
--- a/src/components/admin/users/user-card.tsx
+++ b/src/components/admin/users/user-card.tsx
@@ -12,6 +12,7 @@ import {
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { ConfirmationDialog } from '@/components/shared/confirmation-dialog';
+import { StatusPill } from '@/components/ui/status-pill';
import {
ListCard,
ListCardAvatar,
@@ -169,13 +170,9 @@ export function UserCard({
{/* Status + super-admin pills */}
- {!user.isActive ? (
-
- Inactive
-
- ) : null}
+ {!user.isActive ?
Disabled : null}
{user.isSuperAdmin ? (
-
+
Super Admin
) : null}
diff --git a/src/components/admin/users/user-list.tsx b/src/components/admin/users/user-list.tsx
index f1332f79..19bf19c3 100644
--- a/src/components/admin/users/user-list.tsx
+++ b/src/components/admin/users/user-list.tsx
@@ -11,6 +11,7 @@ import { ConfirmationDialog } from '@/components/shared/confirmation-dialog';
import { PermissionGate } from '@/components/shared/permission-gate';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
+import { StatusPill } from '@/components/ui/status-pill';
import { apiFetch } from '@/lib/api/client';
import { formatRole } from '@/lib/constants';
import { UserCard } from './user-card';
@@ -98,15 +99,15 @@ export function UserList() {
header: 'Status',
cell: ({ row }) =>
row.original.isActive ? (
-
-
+
+
Active
-
+
) : (
-
-
+
+
Disabled
-
+
),
},
{
diff --git a/src/components/berths/berth-card.tsx b/src/components/berths/berth-card.tsx
index 61d5b935..e8fba621 100644
--- a/src/components/berths/berth-card.tsx
+++ b/src/components/berths/berth-card.tsx
@@ -12,23 +12,23 @@ import {
} from '@/components/ui/dropdown-menu';
import { TagBadge } from '@/components/shared/tag-badge';
import { ListCard, ListCardAvatar, ListCardMeta } from '@/components/shared/list-card';
-import { cn } from '@/lib/utils';
+import { StatusPill, type StatusPillStatus } from '@/components/ui/status-pill';
import { formatCurrency } from '@/lib/utils/currency';
import type { BerthRow } from './berth-columns';
import { mooringLetterDot } from './mooring-letter-tone';
-const STATUS_VARIANTS: Record = {
- available: 'bg-green-100 text-green-800 border-green-200',
- under_offer: 'bg-yellow-100 text-yellow-800 border-yellow-200',
- sold: 'bg-red-100 text-red-800 border-red-200',
-};
-
const STATUS_LABELS: Record = {
available: 'Available',
under_offer: 'Under Offer',
sold: 'Sold',
};
+const BERTH_STATUS_PILL: Record = {
+ available: 'available',
+ under_offer: 'under_offer',
+ sold: 'sold',
+};
+
interface BerthCardProps {
berth: BerthRow;
}
@@ -39,8 +39,7 @@ export function BerthCard({ berth }: BerthCardProps) {
const portSlug = params?.portSlug ?? '';
const statusLabel = STATUS_LABELS[berth.status] ?? berth.status;
- const statusColor =
- STATUS_VARIANTS[berth.status] ?? 'bg-muted text-muted-foreground border-muted';
+ const statusPill = BERTH_STATUS_PILL[berth.status] ?? 'pending';
// Accent stripe groups visually by dock (A-row, B-row, ...). Status is
// already conveyed by the pill below, so the stripe is dock-keyed.
const accentClass = mooringLetterDot(berth.mooringNumber) ?? 'bg-slate-300';
@@ -168,14 +167,7 @@ export function BerthCard({ berth }: BerthCardProps) {
{/* Status pill + tags */}
-
- {statusLabel}
-
+ {statusLabel}
{tags.slice(0, 2).map((tag) => (
))}
diff --git a/src/components/berths/berth-columns.tsx b/src/components/berths/berth-columns.tsx
index 9df3bcda..55d4ce35 100644
--- a/src/components/berths/berth-columns.tsx
+++ b/src/components/berths/berth-columns.tsx
@@ -12,6 +12,7 @@ import {
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { TagBadge } from '@/components/shared/tag-badge';
+import { StatusPill, type StatusPillStatus } from '@/components/ui/status-pill';
import { formatCurrency } from '@/lib/utils/currency';
import { mooringLetterDot } from './mooring-letter-tone';
import { stageBadgeClass, stageLabel } from '@/lib/constants';
@@ -112,25 +113,23 @@ export const BERTH_DEFAULT_HIDDEN: string[] = [
'pricingValidUntil',
];
+const BERTH_STATUS_PILL: Record = {
+ available: 'available',
+ under_offer: 'under_offer',
+ sold: 'sold',
+};
+
+const BERTH_STATUS_LABELS: Record = {
+ available: 'Available',
+ under_offer: 'Under Offer',
+ sold: 'Sold',
+};
+
function StatusBadge({ status }: { status: string }) {
- const variants: Record = {
- available: 'bg-green-100 text-green-800 border-green-200',
- under_offer: 'bg-yellow-100 text-yellow-800 border-yellow-200',
- sold: 'bg-red-100 text-red-800 border-red-200',
- };
-
- const labels: Record = {
- available: 'Available',
- under_offer: 'Under Offer',
- sold: 'Sold',
- };
-
return (
-
- {labels[status] ?? status}
-
+
+ {BERTH_STATUS_LABELS[status] ?? status}
+
);
}
diff --git a/src/components/berths/berth-detail-header.tsx b/src/components/berths/berth-detail-header.tsx
index 743407d3..7b5368a0 100644
--- a/src/components/berths/berth-detail-header.tsx
+++ b/src/components/berths/berth-detail-header.tsx
@@ -26,6 +26,7 @@ import {
import { Textarea } from '@/components/ui/textarea';
import { DetailHeaderStrip } from '@/components/shared/detail-header-strip';
import { PermissionGate } from '@/components/shared/permission-gate';
+import { StatusPill, type StatusPillStatus } from '@/components/ui/status-pill';
import { BerthForm } from './berth-form';
import { mooringLetterDot } from './mooring-letter-tone';
import { cn } from '@/lib/utils';
@@ -86,18 +87,18 @@ interface BerthDetailHeaderProps {
berth: BerthDetailData;
}
-const STATUS_COLORS: Record = {
- available: 'bg-green-100 text-green-800 border-green-300',
- under_offer: 'bg-yellow-100 text-yellow-800 border-yellow-300',
- sold: 'bg-red-100 text-red-800 border-red-300',
-};
-
const STATUS_LABELS: Record = {
available: 'Available',
under_offer: 'Under Offer',
sold: 'Sold',
};
+const BERTH_STATUS_PILL: Record = {
+ available: 'available',
+ under_offer: 'under_offer',
+ sold: 'sold',
+};
+
interface InterestOption {
id: string;
clientName: string;
@@ -276,11 +277,12 @@ export function BerthDetailHeader({ berth }: BerthDetailHeaderProps) {
>
{berth.mooringNumber}
-
{STATUS_LABELS[berth.status] ?? berth.status}
-
+
diff --git a/src/components/clients/client-interests-tab.tsx b/src/components/clients/client-interests-tab.tsx
index 2a681833..f376f25e 100644
--- a/src/components/clients/client-interests-tab.tsx
+++ b/src/components/clients/client-interests-tab.tsx
@@ -97,7 +97,7 @@ function lastActivityFor(interest: ClientInterestRow): string | null {
}
/** Full interest record returned by `/api/v1/interests/[id]`. Only the fields
- * the drawer actually reads are typed here; the API returns more. */
+ * the preview sheet actually reads are typed here; the API returns more. */
interface InterestDetail {
id: string;
pipelineStage: string;
@@ -114,7 +114,7 @@ interface InterestDetail {
function useInterestDetail(id: string | null) {
return useQuery<{ data: InterestDetail }>({
- queryKey: ['interest-detail-drawer', id],
+ queryKey: ['interest-detail-preview', id],
queryFn: () => apiFetch<{ data: InterestDetail }>(`/api/v1/interests/${id}`),
enabled: id !== null,
// Detail rarely changes during a single drawer-open session; stale-time
@@ -132,7 +132,7 @@ function formatDate(value: string | null | undefined): string | null {
return format(d, 'MMM d, yyyy');
}
-/** A single milestone row inside the drawer's milestone summary. Filled
+/** A single milestone row inside the preview sheet's milestone summary. Filled
* circle when the step is done, hollow when pending. Trailing meta line
* shows the date stamp or a "pending" hint. */
function MilestoneRow({
@@ -162,14 +162,13 @@ function MilestoneRow({
}
/**
- * Bottom-sheet preview of a single interest. Designed for the mobile
- * "tap an interest → see what's happening without leaving the client
- * page" flow. Shows the pipeline progress, a compact milestone summary
- * (EOI / Deposit / Contract), lead context, last contact, and a notes
- * teaser. Tap-out / drag-down dismisses; the full edit page is one tap
- * away via "Open full page →".
+ * Right-side sheet preview of a single interest. "Tap an interest → see
+ * what's happening without leaving the client page". Shows the pipeline
+ * progress, a compact milestone summary (EOI / Deposit / Contract),
+ * lead context, last contact, and a notes teaser. Tap-out / Esc
+ * dismisses; the full edit page is one tap away via "Open full page →".
*/
-function InterestPreviewDrawer({
+function InterestPreviewSheet({
interest,
portSlug,
onClose,
@@ -178,10 +177,10 @@ function InterestPreviewDrawer({
portSlug: string;
onClose: () => void;
}) {
- // Pin the most recently selected interest so the drawer stays populated
- // during the close-animation tail (Vaul keeps the content mounted ~250ms
- // after `open=false`). Conditional setState is safe here - the guard
- // ensures it only fires when the prop actually changes to a new row.
+ // Pin the most recently selected interest so the sheet stays populated
+ // during the close-animation tail (Radix keeps the content mounted
+ // through the slide-out). Conditional setState is safe here - the
+ // guard ensures it only fires when the prop actually changes.
const [pinned, setPinned] = useState(interest);
if (interest && interest !== pinned) setPinned(interest);
const showing = pinned;
@@ -448,7 +447,7 @@ export function ClientInterestsTab({ clientId }: ClientInterestsTabProps) {
) : null}
- setPreviewInterest(null)}
diff --git a/src/components/ui/status-pill.tsx b/src/components/ui/status-pill.tsx
index 5b1d065b..73fd5741 100644
--- a/src/components/ui/status-pill.tsx
+++ b/src/components/ui/status-pill.tsx
@@ -29,6 +29,13 @@ const statusPillVariants = cva(
// Delivered (non-signature docs in hub)
delivered: 'border-purple-light bg-purple-light/40 text-purple-dark',
draft: 'border-slate-200 bg-white text-slate-600',
+ // Berth lifecycle
+ available: 'border-success-border bg-success-bg text-success',
+ under_offer: 'border-warning-border bg-warning-bg text-warning',
+ sold: 'border-error-border bg-error-bg text-error',
+ // User/account lifecycle
+ enabled: 'border-success-border bg-success-bg text-success',
+ disabled: 'border-slate-200 bg-slate-100 text-slate-500',
},
},
defaultVariants: {