refactor(ts): convert Drift + Overdue panels to TSX (B4)
DriftInsightPanel (new @/types DriftBill — its own row shape, money in dollars) and OverdueCommandCenter (TrackerRow[]). Note: TS flags `Date - Date` arithmetic → use .getTime(); the overdue/net-delta totals need asDollars at the fmt boundary. typecheck 0, build green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
c1ec0a4f2e
commit
ee31513dc9
|
|
@ -1,13 +1,15 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { TrendingUp, TrendingDown, ChevronDown } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { api } from '@/api';
|
||||
import { cn, fmt } from '@/lib/utils';
|
||||
import { asDollars } from '@/lib/money';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@/components/ui/collapsible';
|
||||
import type { DriftBill } from '@/types';
|
||||
|
||||
function DriftRow({ row, refresh }) {
|
||||
function DriftRow({ row, refresh }: { row: DriftBill; refresh: () => void }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
async function handleUpdate() {
|
||||
|
|
@ -131,7 +133,7 @@ function DriftRow({ row, refresh }) {
|
|||
);
|
||||
}
|
||||
|
||||
export default function DriftInsightPanel({ driftBills, refresh }) {
|
||||
export default function DriftInsightPanel({ driftBills, refresh }: { driftBills?: DriftBill[]; refresh: () => void }) {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
if (!driftBills?.length) return null;
|
||||
|
|
@ -159,7 +161,7 @@ export default function DriftInsightPanel({ driftBills, refresh }) {
|
|||
: `${driftBills.length} bills changed price`}
|
||||
</span>
|
||||
<span className={cn('font-mono text-sm', netColor)}>
|
||||
{sign}{fmt(Math.abs(totalNetDelta))}/mo net
|
||||
{sign}{fmt(asDollars(Math.abs(totalNetDelta)))}/mo net
|
||||
</span>
|
||||
</div>
|
||||
<ChevronDown
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { AlertCircle, ChevronDown, BellOff, SkipForward, CreditCard } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { api } from '@/api';
|
||||
import { cn, fmt, localDateString } from '@/lib/utils';
|
||||
import { asDollars } from '@/lib/money';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@/components/ui/collapsible';
|
||||
|
|
@ -12,24 +13,33 @@ import {
|
|||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import type { TrackerRow } from '@/types';
|
||||
|
||||
function snoozeUntil(days) {
|
||||
function snoozeUntil(days: number): string {
|
||||
const d = new Date();
|
||||
d.setDate(d.getDate() + days);
|
||||
return localDateString(d);
|
||||
}
|
||||
|
||||
function daysOverdueLabel(dueDate) {
|
||||
function daysOverdueLabel(dueDate: string | null | undefined): string {
|
||||
if (!dueDate) return 'overdue';
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
const due = new Date(`${dueDate}T00:00:00`);
|
||||
const days = Math.floor((today - due) / 86_400_000);
|
||||
const days = Math.floor((today.getTime() - due.getTime()) / 86_400_000);
|
||||
if (days <= 0) return 'due today';
|
||||
return `${days} ${days === 1 ? 'day' : 'days'} overdue`;
|
||||
}
|
||||
|
||||
function OverdueRow({ row, year, month, onPayNow, onRefresh }) {
|
||||
interface OverdueRowProps {
|
||||
row: TrackerRow;
|
||||
year: number;
|
||||
month: number;
|
||||
onPayNow: (row: TrackerRow) => void;
|
||||
onRefresh: () => void;
|
||||
}
|
||||
|
||||
function OverdueRow({ row, year, month, onPayNow, onRefresh }: OverdueRowProps) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const threshold = row.actual_amount ?? row.expected_amount;
|
||||
|
||||
|
|
@ -51,7 +61,7 @@ function OverdueRow({ row, year, month, onPayNow, onRefresh }) {
|
|||
}
|
||||
}
|
||||
|
||||
async function handleSnooze(days) {
|
||||
async function handleSnooze(days: number) {
|
||||
setLoading(true);
|
||||
try {
|
||||
await api.snoozeOverdue(row.id, {
|
||||
|
|
@ -141,7 +151,15 @@ function OverdueRow({ row, year, month, onPayNow, onRefresh }) {
|
|||
);
|
||||
}
|
||||
|
||||
export default function OverdueCommandCenter({ rows, year, month, refresh, onPayNow }) {
|
||||
interface OverdueCommandCenterProps {
|
||||
rows: TrackerRow[];
|
||||
year: number;
|
||||
month: number;
|
||||
refresh: () => void;
|
||||
onPayNow: (row: TrackerRow) => void;
|
||||
}
|
||||
|
||||
export default function OverdueCommandCenter({ rows, year, month, refresh, onPayNow }: OverdueCommandCenterProps) {
|
||||
const todayStr = localDateString();
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
|
|
@ -181,7 +199,7 @@ export default function OverdueCommandCenter({ rows, year, month, refresh, onPay
|
|||
</span>
|
||||
{overdueRows.length > 0 && (
|
||||
<span className="font-mono text-sm text-rose-400 dark:text-rose-300">
|
||||
{fmt(totalOverdue)}
|
||||
{fmt(asDollars(totalOverdue))}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -14,6 +14,19 @@ import type { Dollars } from '@/lib/money';
|
|||
export type TrackerStatus =
|
||||
| 'paid' | 'autodraft' | 'upcoming' | 'due_soon' | 'late' | 'missed' | 'skipped';
|
||||
|
||||
// A price-drift finding (driftService.getDriftReport) — a bill whose recent
|
||||
// payments trend away from its expected amount.
|
||||
export interface DriftBill {
|
||||
id: number;
|
||||
name: string;
|
||||
category_name?: string | null;
|
||||
expected_amount: Dollars;
|
||||
recent_amount: Dollars;
|
||||
direction: string;
|
||||
drift_pct: number;
|
||||
months_sampled: number;
|
||||
}
|
||||
|
||||
// A suggested autopay payment (trackerService.applyAutopaySuggestions).
|
||||
export interface AutopaySuggestion {
|
||||
bill_id: number;
|
||||
|
|
|
|||
Loading…
Reference in New Issue