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';
|
||||
|
||||
export function FilterChip({ active, children, onClick }) {
|
||||
interface FilterChipProps {
|
||||
active?: boolean;
|
||||
children?: ReactNode;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export function FilterChip({ active, children, onClick }: FilterChipProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -1,7 +1,18 @@
|
|||
import { cn, fmt } from '@/lib/utils';
|
||||
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 barTone = summary.remaining === 0
|
||||
? 'bg-emerald-500'
|
||||
|
|
@ -2,8 +2,16 @@ import React, { useMemo } from 'react';
|
|||
import { Loader2, AlertCircle } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
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 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 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: {
|
||||
label: 'Starting',
|
||||
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;
|
||||
|
||||
const { direction, percent_change } = trend;
|
||||
|
||||
let icon, color, text;
|
||||
let icon: string, color: string, text: string;
|
||||
switch (direction) {
|
||||
case 'up':
|
||||
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 isActive = def.activateWhen(value || 0);
|
||||
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;
|
||||
|
||||
return (
|
||||
Loading…
Reference in New Issue