74 lines
3.1 KiB
TypeScript
74 lines
3.1 KiB
TypeScript
"use client";
|
|
|
|
import { useMemo } from "react";
|
|
import useEntries from "@/hooks/use-entries";
|
|
import { useGroupsContext } from "@/hooks/groups-context";
|
|
|
|
function monthlyMultiplier(frequency: string, intervalCount: number) {
|
|
const count = intervalCount || 1;
|
|
switch (frequency) {
|
|
case "DAILY":
|
|
return (30 / count);
|
|
case "WEEKLY":
|
|
return (52 / 12) / count;
|
|
case "BIWEEKLY":
|
|
return (26 / 12) / count;
|
|
case "MONTHLY":
|
|
return (1 / count);
|
|
case "QUARTERLY":
|
|
return (1 / 3) / count;
|
|
case "YEARLY":
|
|
return (1 / 12) / count;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
export default function RecurringEntriesPanel() {
|
|
const { activeGroupId } = useGroupsContext();
|
|
const { entries, loading } = useEntries(activeGroupId);
|
|
|
|
const recurring = useMemo(() => entries.filter(entry => entry.isRecurring), [entries]);
|
|
|
|
return (
|
|
<div className="panel panel-accent p-4">
|
|
<div className="card-header">
|
|
<h2 className="card-title text-lg">Recurring entries</h2>
|
|
</div>
|
|
<div className="mt-3 space-y-2">
|
|
{!activeGroupId ? (
|
|
<div className="text-sm text-muted">Select a group to view recurring entries.</div>
|
|
) : loading ? (
|
|
<div className="space-y-2">
|
|
{[0, 1].map(row => (
|
|
<div key={row} className="rounded-lg border border-accent-weak bg-panel px-3 py-3">
|
|
<div className="animate-pulse space-y-2">
|
|
<div className="h-4 w-28 rounded bg-surface" />
|
|
<div className="h-3 w-40 rounded bg-surface" />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : recurring.length ? (
|
|
recurring.map(entry => {
|
|
const monthly = entry.frequency ? monthlyMultiplier(entry.frequency, entry.intervalCount) * entry.amountDollars : 0;
|
|
return (
|
|
<div key={entry.id} className="rounded-lg border border-accent-weak bg-panel px-3 py-3 text-sm">
|
|
<div className="flex items-center justify-between">
|
|
<div className="font-semibold">${entry.amountDollars.toFixed(2)} · {entry.tags.join(", ") || "No tags"}</div>
|
|
<div className="text-xs text-soft">{entry.entryType}</div>
|
|
</div>
|
|
<div className="mt-1 text-xs text-soft">
|
|
Next run: {entry.nextRunAt || entry.occurredAt} · Monthly est: ${monthly.toFixed(2)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})
|
|
) : (
|
|
<div className="text-sm text-muted">No recurring entries yet.</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|