Compare commits
1 Commits
1.1.2
...
13f091a82c
| Author | SHA1 | Date | |
|---|---|---|---|
| 13f091a82c |
@@ -11,13 +11,21 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
import ConfigurableDashboard from "./components/Dashboard";
|
import ConfigurableDashboard from "./components/Dashboard";
|
||||||
|
import { DashboardState } from "./components/Dashboard/Dashboard.models";
|
||||||
import { configuration } from "./dashboard-config";
|
import { configuration } from "./dashboard-config";
|
||||||
import {
|
import {
|
||||||
useReport,
|
useReport,
|
||||||
prepareReport,
|
prepareReport,
|
||||||
} from "./features/report";
|
} from "./features/report";
|
||||||
|
|
||||||
|
/** Map the internal UI mode to the API flow param */
|
||||||
|
function modeToFlow(mode: "expense" | "income"): "outflows" | "inflows" {
|
||||||
|
return mode === "expense" ? "outflows" : "inflows";
|
||||||
|
}
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
|
const [mode, setMode] = React.useState<"expense" | "income">("expense");
|
||||||
|
|
||||||
const [appliedPayees, setAppliedPayees] = React.useState<string[]>([]);
|
const [appliedPayees, setAppliedPayees] = React.useState<string[]>([]);
|
||||||
const [appliedTags, setAppliedTags] = React.useState<string[]>([]);
|
const [appliedTags, setAppliedTags] = React.useState<string[]>([]);
|
||||||
|
|
||||||
@@ -28,10 +36,8 @@ export default function Dashboard() {
|
|||||||
const [loadedTags, setLoadedTags] = React.useState<string[]>([]);
|
const [loadedTags, setLoadedTags] = React.useState<string[]>([]);
|
||||||
|
|
||||||
const report = useReport({
|
const report = useReport({
|
||||||
periods: ["weekly", "monthly", "full"],
|
periods: ["weekly", "monthly", "all"],
|
||||||
rolling: true,
|
flow: modeToFlow(mode),
|
||||||
include_transactions: true,
|
|
||||||
group_by: ["tags"],
|
|
||||||
payee: appliedPayees.length > 0 ? appliedPayees : undefined,
|
payee: appliedPayees.length > 0 ? appliedPayees : undefined,
|
||||||
tags: appliedTags.length > 0 ? appliedTags : undefined,
|
tags: appliedTags.length > 0 ? appliedTags : undefined,
|
||||||
});
|
});
|
||||||
@@ -43,10 +49,7 @@ export default function Dashboard() {
|
|||||||
report.data.data.buckets.forEach((b: any) => {
|
report.data.data.buckets.forEach((b: any) => {
|
||||||
Object.values(b.periods).forEach((periodArray: any) => {
|
Object.values(b.periods).forEach((periodArray: any) => {
|
||||||
periodArray?.forEach((p: any) => {
|
periodArray?.forEach((p: any) => {
|
||||||
p.expenses?.transactions?.forEach((t: any) => {
|
p.metric?.transactions?.forEach((t: any) => {
|
||||||
if (t.payee?.name) pSet.add(t.payee.name);
|
|
||||||
});
|
|
||||||
p.incomes?.transactions?.forEach((t: any) => {
|
|
||||||
if (t.payee?.name) pSet.add(t.payee.name);
|
if (t.payee?.name) pSet.add(t.payee.name);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -60,10 +63,7 @@ export default function Dashboard() {
|
|||||||
report.data.data.buckets.forEach((b: any) => {
|
report.data.data.buckets.forEach((b: any) => {
|
||||||
Object.values(b.periods).forEach((periodArray: any) => {
|
Object.values(b.periods).forEach((periodArray: any) => {
|
||||||
periodArray?.forEach((p: any) => {
|
periodArray?.forEach((p: any) => {
|
||||||
p.expenses?.transactions?.forEach((t: any) => {
|
p.metric?.transactions?.forEach((t: any) => {
|
||||||
t.tags?.forEach((tag: any) => tSet.add(tag.name || tag));
|
|
||||||
});
|
|
||||||
p.incomes?.transactions?.forEach((t: any) => {
|
|
||||||
t.tags?.forEach((tag: any) => tSet.add(tag.name || tag));
|
t.tags?.forEach((tag: any) => tSet.add(tag.name || tag));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -77,6 +77,10 @@ export default function Dashboard() {
|
|||||||
const isLoading = report.isLoading;
|
const isLoading = report.isLoading;
|
||||||
const error = report.error;
|
const error = report.error;
|
||||||
|
|
||||||
|
/** Callback for the ConfigurableDashboard's mode toggle */
|
||||||
|
const handleModeChange = React.useCallback((newState: DashboardState) => {
|
||||||
|
setMode(newState.mode);
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (isLoading && !report.data) {
|
if (isLoading && !report.data) {
|
||||||
return (
|
return (
|
||||||
@@ -152,7 +156,7 @@ export default function Dashboard() {
|
|||||||
setAppliedTags(tagsInput);
|
setAppliedTags(tagsInput);
|
||||||
}}
|
}}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
sx={{ height: 40, borderRadius: 2 }} // Changed from 56 to 40 to match minHeight of inputs
|
sx={{ height: 40, borderRadius: 2 }}
|
||||||
>
|
>
|
||||||
Apply
|
Apply
|
||||||
</Button>
|
</Button>
|
||||||
@@ -161,6 +165,7 @@ export default function Dashboard() {
|
|||||||
<ConfigurableDashboard
|
<ConfigurableDashboard
|
||||||
config={configuration}
|
config={configuration}
|
||||||
data={data}
|
data={data}
|
||||||
|
onModeChange={handleModeChange}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -50,4 +50,5 @@ export interface DashboardConfig {
|
|||||||
export interface DashboardProps {
|
export interface DashboardProps {
|
||||||
config: DashboardConfig;
|
config: DashboardConfig;
|
||||||
data: ReportData;
|
data: ReportData;
|
||||||
|
onModeChange?: (state: DashboardState) => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,10 +12,14 @@ export default function Dashboard(props: DashboardProps) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const toggleMode = () => {
|
const toggleMode = () => {
|
||||||
setState(prev => ({
|
setState(prev => {
|
||||||
|
const next = {
|
||||||
...prev,
|
...prev,
|
||||||
mode: prev.mode === "expense" ? "income" : "expense",
|
mode: prev.mode === "expense" ? "income" as const : "expense" as const,
|
||||||
}));
|
};
|
||||||
|
props.onModeChange?.(next);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const togglePeriodType = () => {
|
const togglePeriodType = () => {
|
||||||
|
|||||||
@@ -9,15 +9,14 @@ import { ChartDataPoint } from "./HistoryChart.models";
|
|||||||
// ─── Tab → PeriodKey ─────────────────────────────────────────
|
// ─── Tab → PeriodKey ─────────────────────────────────────────
|
||||||
|
|
||||||
const TAB_TO_KEY: Record<string, PeriodKey> = {
|
const TAB_TO_KEY: Record<string, PeriodKey> = {
|
||||||
|
Daily: "daily",
|
||||||
Weekly: "weekly",
|
Weekly: "weekly",
|
||||||
Monthly: "monthly",
|
Monthly: "monthly",
|
||||||
Yearly: "yearly",
|
"All Time": "all",
|
||||||
"Financial Year": "fyly",
|
|
||||||
"All Time": "full",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function tabToKey(tab: string): PeriodKey {
|
export function tabToKey(tab: string): PeriodKey {
|
||||||
return TAB_TO_KEY[tab] ?? "full";
|
return TAB_TO_KEY[tab] ?? "all";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Comparison ──────────────────────────────────────────────
|
// ─── Comparison ──────────────────────────────────────────────
|
||||||
@@ -27,10 +26,9 @@ function attachComparison(
|
|||||||
key: PeriodKey
|
key: PeriodKey
|
||||||
): ChartDataPoint[] {
|
): ChartDataPoint[] {
|
||||||
const getCompareIndex = (i: number) => {
|
const getCompareIndex = (i: number) => {
|
||||||
|
if (key === "daily") return i - 7;
|
||||||
if (key === "weekly") return i - 4;
|
if (key === "weekly") return i - 4;
|
||||||
if (key === "monthly") return i - 12;
|
if (key === "monthly") return i - 12;
|
||||||
if (key === "yearly") return i - 1;
|
|
||||||
if (key === "fyly") return i - 1;
|
|
||||||
return -1;
|
return -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -64,7 +62,7 @@ export function buildChartData(
|
|||||||
let points: ChartDataPoint[] = merged.map((p) => ({
|
let points: ChartDataPoint[] = merged.map((p) => ({
|
||||||
id: p.id,
|
id: p.id,
|
||||||
label: p.label,
|
label: p.label,
|
||||||
amount: getAmount(p, mode),
|
amount: getAmount(p),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (comparison) {
|
if (comparison) {
|
||||||
|
|||||||
@@ -35,11 +35,10 @@ export default function HistoryChart(props: HistoryChartProps) {
|
|||||||
: 1;
|
: 1;
|
||||||
|
|
||||||
const visibleCountMap = {
|
const visibleCountMap = {
|
||||||
|
daily: 7,
|
||||||
weekly: 6,
|
weekly: 6,
|
||||||
monthly: 4,
|
monthly: 4,
|
||||||
yearly: 4,
|
all: 4,
|
||||||
fyly: 4,
|
|
||||||
full: 4,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const visibleCount = visibleCountMap[activeDataKey] ?? 4;
|
const visibleCount = visibleCountMap[activeDataKey] ?? 4;
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ function extractTransactions(
|
|||||||
reportData: ReportData,
|
reportData: ReportData,
|
||||||
selectedPeriodId: string | null,
|
selectedPeriodId: string | null,
|
||||||
selectedGroupKey: GroupKey | null,
|
selectedGroupKey: GroupKey | null,
|
||||||
mode: "expense" | "income"
|
|
||||||
): Transaction[] {
|
): Transaction[] {
|
||||||
const buckets = filterBuckets(reportData.buckets, selectedGroupKey);
|
const buckets = filterBuckets(reportData.buckets, selectedGroupKey);
|
||||||
if (selectedPeriodId) {
|
if (selectedPeriodId) {
|
||||||
@@ -23,20 +22,16 @@ function extractTransactions(
|
|||||||
|
|
||||||
if (!selected) return [];
|
if (!selected) return [];
|
||||||
|
|
||||||
return mode === "expense"
|
return selected.metric.transactions || [];
|
||||||
? (selected.expenses.transactions || [])
|
|
||||||
: (selected.incomes.transactions || []);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const periods = mergeBucketPeriods(buckets, "full");
|
const periods = mergeBucketPeriods(buckets, "all");
|
||||||
|
|
||||||
if (!periods.length) return [];
|
if (!periods.length) return [];
|
||||||
|
|
||||||
const full = periods[0];
|
const full = periods[0];
|
||||||
|
|
||||||
return mode === "expense"
|
return full.metric.transactions || [];
|
||||||
? (full.expenses.transactions || [])
|
|
||||||
: (full.incomes.transactions || []);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Main adapter ────────────────────────────────────────────
|
// ─── Main adapter ────────────────────────────────────────────
|
||||||
@@ -47,10 +42,9 @@ export function buildLatestItems(
|
|||||||
selectedGroupKey: GroupKey | null,
|
selectedGroupKey: GroupKey | null,
|
||||||
mode: "expense" | "income"
|
mode: "expense" | "income"
|
||||||
): LatestItem[] {
|
): LatestItem[] {
|
||||||
const txns = extractTransactions(reportData, selectedPeriodId, selectedGroupKey, mode);
|
const txns = extractTransactions(reportData, selectedPeriodId, selectedGroupKey);
|
||||||
|
|
||||||
return txns
|
return txns
|
||||||
.filter((t) => (mode === "expense" ? t.amount < 0 : t.amount >= 0))
|
|
||||||
.sort(
|
.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
new Date(b.occurred_at).getTime() -
|
new Date(b.occurred_at).getTime() -
|
||||||
|
|||||||
@@ -43,17 +43,17 @@ export function extractTopTags(
|
|||||||
const tags = bucket.group_key.tags;
|
const tags = bucket.group_key.tags;
|
||||||
if (!tags || tags.length === 0) continue;
|
if (!tags || tags.length === 0) continue;
|
||||||
|
|
||||||
// Prefer FULL if available
|
// Prefer ALL if available
|
||||||
const fullPeriods = (bucket.periods.full || []) as DecoratedPeriod[];
|
const allPeriods = (bucket.periods.all || []) as DecoratedPeriod[];
|
||||||
|
|
||||||
const periodsToUse = selectedPeriodId
|
const periodsToUse = selectedPeriodId
|
||||||
? (Object.values(bucket.periods).flat() as DecoratedPeriod[])
|
? (Object.values(bucket.periods).flat() as DecoratedPeriod[])
|
||||||
: fullPeriods;
|
: allPeriods;
|
||||||
|
|
||||||
const period = findPeriod(periodsToUse, selectedPeriodId);
|
const period = findPeriod(periodsToUse, selectedPeriodId);
|
||||||
if (!period) continue;
|
if (!period) continue;
|
||||||
|
|
||||||
const amount = getAmount(period, mode);
|
const amount = getAmount(period);
|
||||||
|
|
||||||
for (const tag of tags) {
|
for (const tag of tags) {
|
||||||
tagMap.set(tag, (tagMap.get(tag) || 0) + amount);
|
tagMap.set(tag, (tagMap.get(tag) || 0) + amount);
|
||||||
|
|||||||
@@ -2,11 +2,12 @@ import {
|
|||||||
ReportPeriod,
|
ReportPeriod,
|
||||||
ReportBucket,
|
ReportBucket,
|
||||||
GroupKey,
|
GroupKey,
|
||||||
|
PeriodType,
|
||||||
} from "../features/report";
|
} from "../features/report";
|
||||||
|
|
||||||
// ─── Types ────────────────────────────────────────────────────
|
// ─── Types ────────────────────────────────────────────────────
|
||||||
|
|
||||||
export type PeriodKey = "weekly" | "monthly" | "yearly" | "fyly" | "full";
|
export type PeriodKey = PeriodType;
|
||||||
|
|
||||||
export type DecoratedPeriod = ReportPeriod & {
|
export type DecoratedPeriod = ReportPeriod & {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -16,11 +17,10 @@ export type DecoratedPeriod = ReportPeriod & {
|
|||||||
// ─── Period helpers ───────────────────────────────────────────
|
// ─── Period helpers ───────────────────────────────────────────
|
||||||
|
|
||||||
const PREFIX_TO_KEY: Record<string, PeriodKey> = {
|
const PREFIX_TO_KEY: Record<string, PeriodKey> = {
|
||||||
|
D: "daily",
|
||||||
W: "weekly",
|
W: "weekly",
|
||||||
M: "monthly",
|
M: "monthly",
|
||||||
Y: "yearly",
|
ALL: "all",
|
||||||
FY: "fyly",
|
|
||||||
FULL: "full",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,19 +29,16 @@ const PREFIX_TO_KEY: Record<string, PeriodKey> = {
|
|||||||
*/
|
*/
|
||||||
export function periodIdToKey(periodId: string): PeriodKey {
|
export function periodIdToKey(periodId: string): PeriodKey {
|
||||||
const prefix = periodId.split(":")[0];
|
const prefix = periodId.split(":")[0];
|
||||||
return PREFIX_TO_KEY[prefix] ?? "full";
|
return PREFIX_TO_KEY[prefix] ?? "all";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── Metric helpers ───────────────────────────────────────────
|
// ─── Metric helpers ───────────────────────────────────────────
|
||||||
|
|
||||||
export function getAmount(
|
export function getAmount(period: ReportPeriod): number {
|
||||||
period: ReportPeriod,
|
return period.metric.sum;
|
||||||
mode: "expense" | "income"
|
|
||||||
): number {
|
|
||||||
return mode === "expense" ? period.expenses.sum : period.incomes.sum;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeMetric(a: ReportPeriod["expenses"], b: ReportPeriod["expenses"]) {
|
function mergeMetric(a: ReportPeriod["metric"], b: ReportPeriod["metric"]) {
|
||||||
const sum = a.sum + b.sum;
|
const sum = a.sum + b.sum;
|
||||||
const count = a.count + b.count;
|
const count = a.count + b.count;
|
||||||
|
|
||||||
@@ -78,14 +75,12 @@ export function mergeBucketPeriods(
|
|||||||
if (!existing) {
|
if (!existing) {
|
||||||
map.set(p.id, {
|
map.set(p.id, {
|
||||||
...p,
|
...p,
|
||||||
expenses: { ...p.expenses },
|
metric: { ...p.metric },
|
||||||
incomes: { ...p.incomes },
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
map.set(p.id, {
|
map.set(p.id, {
|
||||||
...existing,
|
...existing,
|
||||||
expenses: mergeMetric(existing.expenses, p.expenses),
|
metric: mergeMetric(existing.metric, p.metric),
|
||||||
incomes: mergeMetric(existing.incomes, p.incomes),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,7 +121,7 @@ export function matchesGroupKey(
|
|||||||
selected: GroupKey
|
selected: GroupKey
|
||||||
): boolean {
|
): boolean {
|
||||||
for (const [dim, values] of Object.entries(selected)) {
|
for (const [dim, values] of Object.entries(selected)) {
|
||||||
const bucketValues = bucket.group_key[dim as keyof GroupKey];
|
const bucketValues = bucket.group_key[dim];
|
||||||
if (!bucketValues) return false;
|
if (!bucketValues) return false;
|
||||||
if (!(values as string[]).every((v) => bucketValues.includes(v)))
|
if (!(values as string[]).every((v) => bucketValues.includes(v)))
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ export type {
|
|||||||
ReportData,
|
ReportData,
|
||||||
ReportBucket,
|
ReportBucket,
|
||||||
ReportPeriod,
|
ReportPeriod,
|
||||||
|
ReportQuery,
|
||||||
GroupKey,
|
GroupKey,
|
||||||
|
PeriodType,
|
||||||
} from './report.models'
|
} from './report.models'
|
||||||
export {
|
export {
|
||||||
prepareReport
|
prepareReport
|
||||||
|
|||||||
@@ -1,29 +1,40 @@
|
|||||||
export interface Payor {
|
export interface Payor {
|
||||||
|
id?: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
username: string;
|
||||||
|
email: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Payee {
|
export interface Payee {
|
||||||
|
type: "merchant" | "person" | "transfer" | "other";
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Account {
|
export interface Account {
|
||||||
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
number: string;
|
number: string;
|
||||||
|
type: "cash" | "bank" | "credit_card" | "wallet" | "other";
|
||||||
|
currency: string;
|
||||||
|
is_active?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Tag {
|
export interface Tag {
|
||||||
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
description: string;
|
parent_id?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Transaction {
|
export interface Transaction {
|
||||||
|
id: string;
|
||||||
payor: Payor;
|
payor: Payor;
|
||||||
payee: Payee;
|
payee: Payee;
|
||||||
amount: number;
|
amount: number;
|
||||||
account: Account;
|
account: Account;
|
||||||
tags: Tag[];
|
tags: Tag[];
|
||||||
occurred_at: Date;
|
occurred_at: string;
|
||||||
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
@@ -41,12 +52,12 @@ export interface ReportMetric {
|
|||||||
// Period
|
// Period
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
|
|
||||||
export interface ReportPeriod {
|
export type PeriodType = "daily" | "weekly" | "monthly" | "all";
|
||||||
start: Date;
|
|
||||||
end: Date;
|
|
||||||
|
|
||||||
expenses: ReportMetric;
|
export interface ReportPeriod {
|
||||||
incomes: ReportMetric;
|
start: string;
|
||||||
|
end: string;
|
||||||
|
metric: ReportMetric;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
@@ -54,46 +65,48 @@ export interface ReportPeriod {
|
|||||||
// -----------------------------
|
// -----------------------------
|
||||||
|
|
||||||
export type GroupKey = {
|
export type GroupKey = {
|
||||||
payee?: string[];
|
[dimension: string]: string[];
|
||||||
tags?: string[];
|
|
||||||
flow?: string[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ReportBucket {
|
export interface ReportBucket {
|
||||||
group_key: GroupKey;
|
group_key: GroupKey;
|
||||||
|
|
||||||
periods: {
|
periods: {
|
||||||
|
daily?: ReportPeriod[];
|
||||||
weekly?: ReportPeriod[];
|
weekly?: ReportPeriod[];
|
||||||
monthly?: ReportPeriod[];
|
monthly?: ReportPeriod[];
|
||||||
yearly?: ReportPeriod[];
|
all?: ReportPeriod[];
|
||||||
fyly?: ReportPeriod[];
|
|
||||||
full?: ReportPeriod[];
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------
|
||||||
|
// Report Query
|
||||||
|
// -----------------------------
|
||||||
|
|
||||||
|
export interface ReportQuery {
|
||||||
|
accounts?: string[] | null;
|
||||||
|
ignore_self?: boolean | null;
|
||||||
|
start_date?: string | null;
|
||||||
|
end_date?: string | null;
|
||||||
|
min_amount?: number | null;
|
||||||
|
max_amount?: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
// Final Report
|
// Final Report
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
|
|
||||||
export interface ReportData {
|
export interface ReportData {
|
||||||
periods: ("weekly" | "monthly" | "yearly" | "fyly" | "full")[];
|
snapshot_id?: string | null;
|
||||||
|
|
||||||
rolling: boolean;
|
flow?: "inflows" | "outflows" | null;
|
||||||
report_date?: string;
|
|
||||||
|
|
||||||
group_by: ("payee" | "tags")[];
|
periods: PeriodType[];
|
||||||
|
|
||||||
ignore_self: boolean;
|
|
||||||
include_transactions: boolean;
|
|
||||||
|
|
||||||
start_date?: string | null;
|
|
||||||
end_date?: string | null;
|
|
||||||
flow?: "expense" | "income" | null;
|
|
||||||
payee?: string[] | null;
|
|
||||||
account?: string[] | null;
|
|
||||||
tags?: string[] | null;
|
tags?: string[] | null;
|
||||||
min_amount?: number | null;
|
payee?: string[] | null;
|
||||||
max_amount?: number | null;
|
|
||||||
|
|
||||||
buckets: ReportBucket[];
|
buckets: ReportBucket[];
|
||||||
|
|
||||||
|
query: ReportQuery;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
ReportData,
|
ReportData,
|
||||||
ReportPeriod
|
ReportPeriod,
|
||||||
|
PeriodType,
|
||||||
} from "./report.models";
|
} from "./report.models";
|
||||||
|
|
||||||
/* ---------- ID BUILDING ---------- */
|
/* ---------- ID BUILDING ---------- */
|
||||||
@@ -13,7 +14,7 @@ function formatDate(d: Date): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildPeriodId(
|
function buildPeriodId(
|
||||||
type: "weekly" | "monthly" | "yearly" | "fyly" | "full",
|
type: PeriodType,
|
||||||
start: Date,
|
start: Date,
|
||||||
end: Date
|
end: Date
|
||||||
): string {
|
): string {
|
||||||
@@ -21,16 +22,14 @@ function buildPeriodId(
|
|||||||
const e = formatDate(end);
|
const e = formatDate(end);
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case "daily":
|
||||||
|
return `D:${s}_${e}`;
|
||||||
case "weekly":
|
case "weekly":
|
||||||
return `W:${s}_${e}`;
|
return `W:${s}_${e}`;
|
||||||
case "monthly":
|
case "monthly":
|
||||||
return `M:${s}_${e}`;
|
return `M:${s}_${e}`;
|
||||||
case "yearly":
|
case "all":
|
||||||
return `Y:${s}_${e}`;
|
return `ALL:${s}_${e}`;
|
||||||
case "fyly":
|
|
||||||
return `FY:${s}_${e}`;
|
|
||||||
case "full":
|
|
||||||
return `FULL:${s}_${e}`;
|
|
||||||
default:
|
default:
|
||||||
return `${s}_${e}`;
|
return `${s}_${e}`;
|
||||||
}
|
}
|
||||||
@@ -60,19 +59,15 @@ const yearFmt = new Intl.DateTimeFormat("en-GB", {
|
|||||||
timeZone: "UTC",
|
timeZone: "UTC",
|
||||||
});
|
});
|
||||||
|
|
||||||
function sameMonth(a: Date, b: Date) {
|
|
||||||
return (
|
|
||||||
a.getUTCFullYear() === b.getUTCFullYear() &&
|
|
||||||
a.getUTCMonth() === b.getUTCMonth()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildLabel(
|
function buildLabel(
|
||||||
type: "weekly" | "monthly" | "yearly" | "fyly" | "full",
|
type: PeriodType,
|
||||||
start: Date,
|
start: Date,
|
||||||
end: Date
|
end: Date
|
||||||
): string {
|
): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case "daily":
|
||||||
|
return dayFmt.format(start);
|
||||||
|
|
||||||
case "weekly": {
|
case "weekly": {
|
||||||
const sDay = start.getUTCDate();
|
const sDay = start.getUTCDate();
|
||||||
const m = monthFmt.format(start);
|
const m = monthFmt.format(start);
|
||||||
@@ -82,15 +77,6 @@ function buildLabel(
|
|||||||
case "monthly":
|
case "monthly":
|
||||||
return `${monthFmt.format(start)} ${yearFmt.format(start)}`;
|
return `${monthFmt.format(start)} ${yearFmt.format(start)}`;
|
||||||
|
|
||||||
case "yearly":
|
|
||||||
return yearFmt.format(start);
|
|
||||||
|
|
||||||
case "fyly": {
|
|
||||||
const startY = start.getUTCFullYear();
|
|
||||||
const endY = end.getUTCFullYear();
|
|
||||||
return `FY ${startY}–${String(endY).slice(-2)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return `${monthDayFmt.format(start)} - ${monthDayFmt.format(end)}`;
|
return `${monthDayFmt.format(start)} - ${monthDayFmt.format(end)}`;
|
||||||
}
|
}
|
||||||
@@ -99,7 +85,7 @@ function buildLabel(
|
|||||||
/* ---------- MAIN ---------- */
|
/* ---------- MAIN ---------- */
|
||||||
|
|
||||||
function decoratePeriods(
|
function decoratePeriods(
|
||||||
type: "weekly" | "monthly" | "yearly" | "fyly" | "full",
|
type: PeriodType,
|
||||||
periods: ReportPeriod[]
|
periods: ReportPeriod[]
|
||||||
): (ReportPeriod & { id: string; label: string })[] {
|
): (ReportPeriod & { id: string; label: string })[] {
|
||||||
return periods.map((p) => ({
|
return periods.map((p) => ({
|
||||||
|
|||||||
@@ -1,20 +1,11 @@
|
|||||||
import { useResourceByName } from "../../../react-openapi";
|
import { useResourceByName } from "../../../react-openapi";
|
||||||
|
|
||||||
export interface ReportParams {
|
export interface ReportParams {
|
||||||
periods?: ("weekly" | "monthly" | "yearly" | "fyly" | "full")[];
|
snapshot_id?: string;
|
||||||
rolling?: boolean;
|
periods?: ("daily" | "weekly" | "monthly" | "all")[];
|
||||||
report_date?: string;
|
flow?: "inflows" | "outflows";
|
||||||
group_by?: ("payee" | "tags")[];
|
|
||||||
ignore_self?: boolean;
|
|
||||||
include_transactions?: boolean;
|
|
||||||
start_date?: string;
|
|
||||||
end_date?: string;
|
|
||||||
flow?: "expense" | "income";
|
|
||||||
payee?: string[];
|
payee?: string[];
|
||||||
account?: string[];
|
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
min_amount?: number;
|
|
||||||
max_amount?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useReport(params: ReportParams) {
|
export function useReport(params: ReportParams) {
|
||||||
@@ -23,6 +14,5 @@ export function useReport(params: ReportParams) {
|
|||||||
return useList({
|
return useList({
|
||||||
...params,
|
...params,
|
||||||
periods: params.periods,
|
periods: params.periods,
|
||||||
group_by: params.group_by,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user