Simpl-Resultat/src/services/dashboardService.ts
Le-King-Fu 29a1a15120
Some checks failed
Release / build (windows-latest) (push) Has been cancelled
feat: add chart patterns, context menu, and import preview popup (v0.2.3)
- Add SVG fill patterns to differentiate chart categories beyond color
- Add right-click context menu on charts to hide categories or view transactions
- Add transaction detail modal showing all transactions for a category
- Change import preview from wizard step to popup modal
- Add direct "Check Duplicates" button skipping preview step
- Bump version to 0.2.3

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 23:55:19 +00:00

158 lines
3.9 KiB
TypeScript

import { getDb } from "./db";
import type {
DashboardSummary,
CategoryBreakdownItem,
RecentTransaction,
TransactionRow,
} from "../shared/types";
export async function getDashboardSummary(
dateFrom?: string,
dateTo?: string
): Promise<DashboardSummary> {
const db = await getDb();
const whereClauses: string[] = [];
const params: unknown[] = [];
let paramIndex = 1;
if (dateFrom) {
whereClauses.push(`date >= $${paramIndex}`);
params.push(dateFrom);
paramIndex++;
}
if (dateTo) {
whereClauses.push(`date <= $${paramIndex}`);
params.push(dateTo);
paramIndex++;
}
const whereSQL =
whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
const rows = await db.select<
Array<{
totalCount: number;
totalAmount: number;
incomeTotal: number;
expenseTotal: number;
}>
>(
`SELECT
COUNT(*) AS totalCount,
COALESCE(SUM(amount), 0) AS totalAmount,
COALESCE(SUM(CASE WHEN amount > 0 THEN amount ELSE 0 END), 0) AS incomeTotal,
COALESCE(SUM(CASE WHEN amount < 0 THEN amount ELSE 0 END), 0) AS expenseTotal
FROM transactions
${whereSQL}`,
params
);
return rows[0] ?? { totalCount: 0, totalAmount: 0, incomeTotal: 0, expenseTotal: 0 };
}
export async function getExpensesByCategory(
dateFrom?: string,
dateTo?: string
): Promise<CategoryBreakdownItem[]> {
const db = await getDb();
const whereClauses: string[] = ["COALESCE(c.type, 'expense') = 'expense'"];
const params: unknown[] = [];
let paramIndex = 1;
if (dateFrom) {
whereClauses.push(`t.date >= $${paramIndex}`);
params.push(dateFrom);
paramIndex++;
}
if (dateTo) {
whereClauses.push(`t.date <= $${paramIndex}`);
params.push(dateTo);
paramIndex++;
}
const whereSQL = `WHERE ${whereClauses.join(" AND ")}`;
return db.select<CategoryBreakdownItem[]>(
`SELECT
t.category_id,
COALESCE(c.name, 'Uncategorized') AS category_name,
COALESCE(c.color, '#9ca3af') AS category_color,
ABS(SUM(t.amount)) AS total
FROM transactions t
LEFT JOIN categories c ON t.category_id = c.id
${whereSQL}
GROUP BY t.category_id
ORDER BY total DESC`,
params
);
}
export async function getTransactionsByCategory(
categoryId: number | null,
dateFrom?: string,
dateTo?: string
): Promise<TransactionRow[]> {
const db = await getDb();
const whereClauses: string[] = [];
const params: unknown[] = [];
let paramIndex = 1;
if (categoryId === null) {
whereClauses.push("t.category_id IS NULL");
} else {
whereClauses.push(`t.category_id = $${paramIndex}`);
params.push(categoryId);
paramIndex++;
}
if (dateFrom) {
whereClauses.push(`t.date >= $${paramIndex}`);
params.push(dateFrom);
paramIndex++;
}
if (dateTo) {
whereClauses.push(`t.date <= $${paramIndex}`);
params.push(dateTo);
paramIndex++;
}
const whereSQL = `WHERE ${whereClauses.join(" AND ")}`;
return db.select<TransactionRow[]>(
`SELECT
t.id, t.date, t.description, t.amount,
t.category_id,
c.name AS category_name,
c.color AS category_color,
s.name AS source_name,
t.notes,
t.is_manually_categorized
FROM transactions t
LEFT JOIN categories c ON t.category_id = c.id
LEFT JOIN import_sources s ON t.source_id = s.id
${whereSQL}
ORDER BY t.date DESC, t.id DESC`,
params
);
}
export async function getRecentTransactions(
limit: number = 10
): Promise<RecentTransaction[]> {
const db = await getDb();
return db.select<RecentTransaction[]>(
`SELECT
t.id, t.date, t.description, t.amount,
c.name AS category_name, c.color AS category_color
FROM transactions t
LEFT JOIN categories c ON t.category_id = c.id
WHERE COALESCE(c.type, 'expense') = 'expense'
ORDER BY t.date DESC, t.id DESC
LIMIT $1`,
[limit]
);
}