Skip to content

Commit 8c0f67c

Browse files
committed
Add pattern delete buttons to subscriptions and income pages
1 parent 43f5275 commit 8c0f67c

4 files changed

Lines changed: 58 additions & 17 deletions

File tree

src/app/(app)/income/page.tsx

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
DialogTitle,
1616
DialogTrigger,
1717
} from "@/components/ui/dialog";
18-
import { Plus, Loader2, Check } from "lucide-react";
18+
import { Plus, Loader2, Check, X } from "lucide-react";
1919
import { F } from "@/components/ui/f";
2020

2121
interface Income {
@@ -157,6 +157,27 @@ export default function IncomePage() {
157157
} catch (err) { console.error("[income] Add pattern error:", err); }
158158
};
159159

160+
const deletePattern = async (patternId: number) => {
161+
try {
162+
await fetch("/api/matches", {
163+
method: "DELETE",
164+
headers: { "Content-Type": "application/json" },
165+
body: JSON.stringify({ id: patternId }),
166+
});
167+
const matchData = await fetch("/api/matches").then((r) => r.json());
168+
if (matchData.patterns) {
169+
const grouped: Record<number, { id: number; payee_pattern: string }[]> = {};
170+
for (const p of matchData.patterns) {
171+
if (p.source_type === "income") {
172+
if (!grouped[p.source_id]) grouped[p.source_id] = [];
173+
grouped[p.source_id].push({ id: p.id, payee_pattern: p.payee_pattern });
174+
}
175+
}
176+
setPatterns(grouped);
177+
}
178+
} catch (err) { console.error("[income] Delete pattern error:", err); }
179+
};
180+
160181
const deleteIncome = async (id: number) => {
161182
console.info("[income] Deleting:", id);
162183
try {
@@ -306,8 +327,13 @@ export default function IncomePage() {
306327
</div>
307328
{(patterns[editTarget.id] || []).length > 0 && (
308329
<div className="match-pattern-list">
309-
{patterns[editTarget.id].map((p, i) => (
310-
<span key={i} className="match-pattern-tag">{p.payee_pattern}</span>
330+
{patterns[editTarget.id].map((p) => (
331+
<div key={p.id} className="match-pattern-item">
332+
<span className="match-pattern-tag">{p.payee_pattern}</span>
333+
<button type="button" className="batch-remove-btn" onClick={() => deletePattern(p.id)}>
334+
<X />
335+
</button>
336+
</div>
311337
))}
312338
</div>
313339
)}

src/app/(app)/subscriptions/page.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
DialogTitle,
1717
DialogTrigger,
1818
} from "@/components/ui/dialog";
19-
import { Plus, Loader2, Check, AlertCircle } from "lucide-react";
19+
import { Plus, Loader2, Check, AlertCircle, X } from "lucide-react";
2020
import { F } from "@/components/ui/f";
2121

2222
// Known brand configs
@@ -144,7 +144,7 @@ interface Subscription {
144144
is_paid: boolean;
145145
is_overdue: boolean;
146146
is_priority: number;
147-
patterns: string[];
147+
patterns: { id: number; pattern: string }[];
148148
}
149149

150150
export default function SubscriptionsPage() {
@@ -274,6 +274,17 @@ export default function SubscriptionsPage() {
274274
} catch (err) { console.error("[subscriptions] Add pattern error:", err); }
275275
};
276276

277+
const deletePattern = async (patternId: number) => {
278+
try {
279+
await fetch("/api/matches", {
280+
method: "DELETE",
281+
headers: { "Content-Type": "application/json" },
282+
body: JSON.stringify({ id: patternId }),
283+
});
284+
loadSubscriptions();
285+
} catch (err) { console.error("[subscriptions] Delete pattern error:", err); }
286+
};
287+
277288
const active = subscriptions.filter((s) => s.is_active);
278289
const monthlyTotal = active.reduce((s, sub) => s + sub.amount, 0);
279290
const yearlyTotal = monthlyTotal * 12;
@@ -353,7 +364,7 @@ export default function SubscriptionsPage() {
353364
</div>
354365
<p className="subscription-card-meta">
355366
{locale === "fi" ? "Veloitus" : "Billing"} {sub.due_day}. {locale === "fi" ? "päivä" : ""}
356-
{sub.patterns.length > 0 && <span className="list-item-patterns">{sub.patterns.join(", ")}</span>}
367+
{sub.patterns.length > 0 && <span className="list-item-patterns">{sub.patterns.map((p) => p.pattern).join(", ")}</span>}
357368
</p>
358369
</div>
359370
<div className="subscription-card-right">
@@ -406,8 +417,13 @@ export default function SubscriptionsPage() {
406417
</div>
407418
{editTarget.patterns.length > 0 && (
408419
<div className="match-pattern-list">
409-
{editTarget.patterns.map((p, i) => (
410-
<span key={i} className="match-pattern-tag">{p}</span>
420+
{editTarget.patterns.map((p) => (
421+
<div key={p.id} className="match-pattern-item">
422+
<span className="match-pattern-tag">{p.pattern}</span>
423+
<button type="button" className="batch-remove-btn" onClick={() => deletePattern(p.id)}>
424+
<X />
425+
</button>
426+
</div>
411427
))}
412428
</div>
413429
)}

src/app/api/subscriptions/route.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ export async function GET() {
2121
.all(month) as { source_id: number; amount: number }[];
2222
const matchMap = new Map(matches.map((m) => [m.source_id, m.amount]));
2323

24-
// Get payee patterns
24+
// Get payee patterns with IDs for deletion
2525
const patterns = db
26-
.prepare("SELECT source_id, payee_pattern FROM payee_matches WHERE source_type = 'subscription'")
27-
.all() as { source_id: number; payee_pattern: string }[];
28-
const patternMap = new Map<number, string[]>();
26+
.prepare("SELECT id, source_id, payee_pattern FROM payee_matches WHERE source_type = 'subscription'")
27+
.all() as { id: number; source_id: number; payee_pattern: string }[];
28+
const patternMap = new Map<number, { id: number; pattern: string }[]>();
2929
for (const p of patterns) {
3030
if (!patternMap.has(p.source_id)) patternMap.set(p.source_id, []);
31-
patternMap.get(p.source_id)!.push(p.payee_pattern);
31+
patternMap.get(p.source_id)!.push({ id: p.id, pattern: p.payee_pattern });
3232
}
3333

3434
// Manual paid status (subscription IDs offset by 10000 to avoid collision with bills)

src/lib/daily-budget.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,8 @@ export function calculateDailyBudget(params: {
7474
}
7575
incomeEvents.sort((a, b) => a.absDay - b.absDay);
7676

77-
// Window end: at least minWindowDays, but extend to cover the next income after that
78-
const minEnd = today + minWindowDays;
79-
const endAbsDay = Math.max(minEnd, incomeEvents.find((e) => e.absDay >= minEnd)?.absDay || minEnd);
77+
// Fixed 14-day window — no extension since we don't count future income
78+
const endAbsDay = today + minWindowDays;
8079

8180
const totalDays = endAbsDay - today;
8281
if (totalDays <= 0) return { dailyBudget: 0, tightestSegment: null, segmentCount: 0 };
@@ -126,7 +125,7 @@ export function calculateDailyBudget(params: {
126125
const pool = balance + windowIncome - windowObligations - windowSaving;
127126
const dailyBudget = Math.max(0, Math.round((pool / totalDays) * 100) / 100);
128127

129-
console.debug("[daily-budget] balance:", Math.round(balance), "windowIncome:", Math.round(windowIncome), "obligations:", Math.round(windowObligations), "saving:", Math.round(windowSaving), "pool:", Math.round(pool), "days:", totalDays, "daily:", dailyBudget);
128+
console.info("[daily-budget] balance:", Math.round(balance), "windowIncome:", Math.round(windowIncome), "obligations:", Math.round(windowObligations), "saving:", Math.round(windowSaving), "pool:", Math.round(pool), "days:", totalDays, "daily:", dailyBudget);
130129

131130
const tightestSegment = {
132131
startDay: today,

0 commit comments

Comments
 (0)