refactor(ts): convert tracker leaf components to TSX (B1)
First .tsx conversions — the tracker leaf components, where the branded Dollars now flows into the UI: StatusBadge (status: TrackerStatus), FilterChip, PaymentProgress (paymentSummary's Dollars → fmt type-checks), SummaryCards (SummaryCard value: Dollars, CardDef/CardType/TrendInfo typed). Props interfaces added; no behavior change. Confirms the .tsx pattern: a typed parent importing untyped .jsx children is fine (children accept any props — no forced cascade). typecheck 0, lint 0 errors, build green, 48 client tests, 17/17 e2e probe (home page renders). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
4af738f947
commit
dc675fbecd
|
|
@ -1,6 +1,13 @@
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
export function FilterChip({ active, children, onClick }) {
|
interface FilterChipProps {
|
||||||
|
active?: boolean;
|
||||||
|
children?: ReactNode;
|
||||||
|
onClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FilterChip({ active, children, onClick }: FilterChipProps) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -1,7 +1,18 @@
|
||||||
import { cn, fmt } from '@/lib/utils';
|
import { cn, fmt } from '@/lib/utils';
|
||||||
import { paymentSummary } from '@/lib/trackerUtils';
|
import { paymentSummary } from '@/lib/trackerUtils';
|
||||||
|
import type { Dollars } from '@/lib/money';
|
||||||
|
import type { TrackerRow } from '@/types';
|
||||||
|
|
||||||
export function PaymentProgress({ row, threshold, onOpen, onMarkFullAmount, compact = false, className }) {
|
interface PaymentProgressProps {
|
||||||
|
row: TrackerRow;
|
||||||
|
threshold: Dollars;
|
||||||
|
onOpen?: () => void;
|
||||||
|
onMarkFullAmount?: () => void;
|
||||||
|
compact?: boolean;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PaymentProgress({ row, threshold, onOpen, onMarkFullAmount, compact = false, className }: PaymentProgressProps) {
|
||||||
const summary = paymentSummary(row, threshold);
|
const summary = paymentSummary(row, threshold);
|
||||||
const barTone = summary.remaining === 0
|
const barTone = summary.remaining === 0
|
||||||
? 'bg-emerald-500'
|
? 'bg-emerald-500'
|
||||||
|
|
@ -2,8 +2,16 @@ import React, { useMemo } from 'react';
|
||||||
import { Loader2, AlertCircle } from 'lucide-react';
|
import { Loader2, AlertCircle } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { STATUS_META, isPaidStatus } from '@/lib/trackerUtils';
|
import { STATUS_META, isPaidStatus } from '@/lib/trackerUtils';
|
||||||
|
import type { TrackerStatus } from '@/types';
|
||||||
|
|
||||||
export const StatusBadge = React.memo(function StatusBadge({ status, clickable, onClick, loading }) {
|
interface StatusBadgeProps {
|
||||||
|
status: TrackerStatus;
|
||||||
|
clickable?: boolean;
|
||||||
|
onClick?: () => void;
|
||||||
|
loading?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StatusBadge = React.memo(function StatusBadge({ status, clickable, onClick, loading }: StatusBadgeProps) {
|
||||||
const meta = useMemo(() => STATUS_META[status] || STATUS_META.upcoming, [status]);
|
const meta = useMemo(() => STATUS_META[status] || STATUS_META.upcoming, [status]);
|
||||||
|
|
||||||
const isSkipped = status === 'skipped';
|
const isSkipped = status === 'skipped';
|
||||||
|
|
@ -1,7 +1,20 @@
|
||||||
import { TrendingUp, AlertCircle, Clock, CheckCircle2, Settings2 } from 'lucide-react';
|
import { TrendingUp, AlertCircle, Clock, CheckCircle2, Settings2, type LucideIcon } from 'lucide-react';
|
||||||
import { cn, fmt } from '@/lib/utils';
|
import { cn, fmt } from '@/lib/utils';
|
||||||
|
import type { Dollars } from '@/lib/money';
|
||||||
|
|
||||||
const CARD_DEFS = {
|
type CardType = 'starting' | 'paid' | 'remaining' | 'overdue';
|
||||||
|
|
||||||
|
interface CardDef {
|
||||||
|
label: string;
|
||||||
|
icon: LucideIcon;
|
||||||
|
bar: string;
|
||||||
|
glow: string;
|
||||||
|
borderActive?: string;
|
||||||
|
valueClass: string;
|
||||||
|
activateWhen: (v: number) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CARD_DEFS: Record<CardType, CardDef> = {
|
||||||
starting: {
|
starting: {
|
||||||
label: 'Starting',
|
label: 'Starting',
|
||||||
icon: TrendingUp,
|
icon: TrendingUp,
|
||||||
|
|
@ -38,12 +51,17 @@ const CARD_DEFS = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function TrendIndicator({ trend }) {
|
interface TrendInfo {
|
||||||
|
direction: string;
|
||||||
|
percent_change: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TrendIndicator({ trend }: { trend?: TrendInfo | null }) {
|
||||||
if (!trend) return null;
|
if (!trend) return null;
|
||||||
|
|
||||||
const { direction, percent_change } = trend;
|
const { direction, percent_change } = trend;
|
||||||
|
|
||||||
let icon, color, text;
|
let icon: string, color: string, text: string;
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case 'up':
|
case 'up':
|
||||||
icon = '↑';
|
icon = '↑';
|
||||||
|
|
@ -73,7 +91,15 @@ export function TrendIndicator({ trend }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SummaryCard({ type, value, onEdit, hint, label }) {
|
interface SummaryCardProps {
|
||||||
|
type: CardType;
|
||||||
|
value?: Dollars;
|
||||||
|
onEdit?: () => void;
|
||||||
|
hint?: string;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SummaryCard({ type, value, onEdit, hint, label }: SummaryCardProps) {
|
||||||
const def = CARD_DEFS[type];
|
const def = CARD_DEFS[type];
|
||||||
const isActive = def.activateWhen(value || 0);
|
const isActive = def.activateWhen(value || 0);
|
||||||
const Icon = def.icon;
|
const Icon = def.icon;
|
||||||
|
|
@ -118,7 +144,7 @@ export function SummaryCard({ type, value, onEdit, hint, label }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TrendCard({ trend }) {
|
export function TrendCard({ trend }: { trend?: TrendInfo | null }) {
|
||||||
if (!trend) return null;
|
if (!trend) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
Loading…
Reference in New Issue