diff --git a/client/components/tracker/DriftInsightPanel.jsx b/client/components/tracker/DriftInsightPanel.tsx
similarity index 95%
rename from client/components/tracker/DriftInsightPanel.jsx
rename to client/components/tracker/DriftInsightPanel.tsx
index 1d84bac..6ad6943 100644
--- a/client/components/tracker/DriftInsightPanel.jsx
+++ b/client/components/tracker/DriftInsightPanel.tsx
@@ -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`}
- {sign}{fmt(Math.abs(totalNetDelta))}/mo net
+ {sign}{fmt(asDollars(Math.abs(totalNetDelta)))}/mo net
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
{overdueRows.length > 0 && (
- {fmt(totalOverdue)}
+ {fmt(asDollars(totalOverdue))}
)}
diff --git a/client/types.ts b/client/types.ts
index 2e63693..383ff3b 100644
--- a/client/types.ts
+++ b/client/types.ts
@@ -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;