mirror of
https://github.com/elisiariocouto/leggen.git
synced 2025-12-14 12:02:19 +00:00
Fix date parsing and add time period filters to Analytics dashboard
Co-authored-by: elisiariocouto <818914+elisiariocouto@users.noreply.github.com>
This commit is contained in:
committed by
Elisiário Couto
parent
b7e4ec4a1b
commit
6bfbed8fb6
@@ -59,11 +59,11 @@ export default function BalanceChart({ data, accounts, className }: BalanceChart
|
|||||||
const chartData = data
|
const chartData = data
|
||||||
.filter((balance) => balance.balance_type === "closingBooked")
|
.filter((balance) => balance.balance_type === "closingBooked")
|
||||||
.map((balance) => ({
|
.map((balance) => ({
|
||||||
date: new Date(balance.reference_date).toLocaleDateString(),
|
date: new Date(balance.reference_date).toLocaleDateString('en-GB'), // DD/MM/YYYY format
|
||||||
balance: balance.balance_amount,
|
balance: balance.balance_amount,
|
||||||
account_id: balance.account_id,
|
account_id: balance.account_id,
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
|
.sort((a, b) => new Date(a.date.split('/').reverse().join('/')).getTime() - new Date(b.date.split('/').reverse().join('/')).getTime());
|
||||||
|
|
||||||
// Group by account and aggregate
|
// Group by account and aggregate
|
||||||
const accountBalances: { [key: string]: ChartDataPoint[] } = {};
|
const accountBalances: { [key: string]: ChartDataPoint[] } = {};
|
||||||
@@ -86,7 +86,7 @@ export default function BalanceChart({ data, accounts, className }: BalanceChart
|
|||||||
});
|
});
|
||||||
|
|
||||||
const finalData = Object.values(aggregatedData).sort(
|
const finalData = Object.values(aggregatedData).sort(
|
||||||
(a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()
|
(a, b) => new Date(a.date.split('/').reverse().join('/')).getTime() - new Date(b.date.split('/').reverse().join('/')).getTime()
|
||||||
);
|
);
|
||||||
|
|
||||||
const colors = ["#3B82F6", "#10B981", "#F59E0B", "#EF4444", "#8B5CF6"];
|
const colors = ["#3B82F6", "#10B981", "#F59E0B", "#EF4444", "#8B5CF6"];
|
||||||
@@ -117,8 +117,10 @@ export default function BalanceChart({ data, accounts, className }: BalanceChart
|
|||||||
dataKey="date"
|
dataKey="date"
|
||||||
tick={{ fontSize: 12 }}
|
tick={{ fontSize: 12 }}
|
||||||
tickFormatter={(value) => {
|
tickFormatter={(value) => {
|
||||||
const date = new Date(value);
|
// Convert DD/MM/YYYY back to a proper date for formatting
|
||||||
return date.toLocaleDateString(undefined, {
|
const [day, month, year] = value.split('/');
|
||||||
|
const date = new Date(year, month - 1, day);
|
||||||
|
return date.toLocaleDateString('en-GB', {
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import apiClient from "../../lib/api";
|
|||||||
|
|
||||||
interface MonthlyTrendsProps {
|
interface MonthlyTrendsProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
days?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MonthlyData {
|
interface MonthlyData {
|
||||||
@@ -31,13 +32,12 @@ interface TooltipProps {
|
|||||||
label?: string;
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MonthlyTrends({ className }: MonthlyTrendsProps) {
|
export default function MonthlyTrends({ className, days = 365 }: MonthlyTrendsProps) {
|
||||||
// Get transactions for the last 12 months using analytics endpoint
|
// Get transactions for the specified period using analytics endpoint
|
||||||
const { data: transactions, isLoading } = useQuery({
|
const { data: transactions, isLoading } = useQuery({
|
||||||
queryKey: ["transactions", "monthly-trends"],
|
queryKey: ["transactions", "monthly-trends", days],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
// Get last 365 days of transactions for monthly trends
|
return await apiClient.getTransactionsForAnalytics(days);
|
||||||
return await apiClient.getTransactionsForAnalytics(365);
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
39
frontend/src/components/analytics/TimePeriodFilter.tsx
Normal file
39
frontend/src/components/analytics/TimePeriodFilter.tsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { Calendar } from "lucide-react";
|
||||||
|
import type { TimePeriod } from "../../lib/timePeriods";
|
||||||
|
import { TIME_PERIODS } from "../../lib/timePeriods";
|
||||||
|
|
||||||
|
interface TimePeriodFilterProps {
|
||||||
|
selectedPeriod: TimePeriod;
|
||||||
|
onPeriodChange: (period: TimePeriod) => void;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TimePeriodFilter({
|
||||||
|
selectedPeriod,
|
||||||
|
onPeriodChange,
|
||||||
|
className = "",
|
||||||
|
}: TimePeriodFilterProps) {
|
||||||
|
return (
|
||||||
|
<div className={`flex items-center gap-4 ${className}`}>
|
||||||
|
<div className="flex items-center gap-2 text-gray-700">
|
||||||
|
<Calendar size={20} />
|
||||||
|
<span className="font-medium">Time Period:</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{TIME_PERIODS.map((period) => (
|
||||||
|
<button
|
||||||
|
key={period.value}
|
||||||
|
onClick={() => onPeriodChange(period)}
|
||||||
|
className={`px-4 py-2 text-sm font-medium rounded-md transition-colors ${
|
||||||
|
selectedPeriod.value === period.value
|
||||||
|
? "bg-blue-600 text-white"
|
||||||
|
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{period.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
19
frontend/src/lib/timePeriods.ts
Normal file
19
frontend/src/lib/timePeriods.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
export type TimePeriod = {
|
||||||
|
label: string;
|
||||||
|
days: number;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getDaysFromYearStart(): number {
|
||||||
|
const now = new Date();
|
||||||
|
const yearStart = new Date(now.getFullYear(), 0, 1);
|
||||||
|
const diffTime = now.getTime() - yearStart.getTime();
|
||||||
|
return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TIME_PERIODS: TimePeriod[] = [
|
||||||
|
{ label: "Last 30 days", days: 30, value: "30d" },
|
||||||
|
{ label: "Last 6 months", days: 180, value: "6m" },
|
||||||
|
{ label: "Year to Date", days: getDaysFromYearStart(), value: "ytd" },
|
||||||
|
{ label: "Last 365 days", days: 365, value: "365d" },
|
||||||
|
];
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
CreditCard,
|
CreditCard,
|
||||||
TrendingUp,
|
TrendingUp,
|
||||||
@@ -13,12 +14,20 @@ import StatCard from "../components/analytics/StatCard";
|
|||||||
import BalanceChart from "../components/analytics/BalanceChart";
|
import BalanceChart from "../components/analytics/BalanceChart";
|
||||||
import TransactionDistribution from "../components/analytics/TransactionDistribution";
|
import TransactionDistribution from "../components/analytics/TransactionDistribution";
|
||||||
import MonthlyTrends from "../components/analytics/MonthlyTrends";
|
import MonthlyTrends from "../components/analytics/MonthlyTrends";
|
||||||
|
import TimePeriodFilter from "../components/analytics/TimePeriodFilter";
|
||||||
|
import type { TimePeriod } from "../lib/timePeriods";
|
||||||
|
import { TIME_PERIODS } from "../lib/timePeriods";
|
||||||
|
|
||||||
function AnalyticsDashboard() {
|
function AnalyticsDashboard() {
|
||||||
|
// Default to Last 365 days
|
||||||
|
const [selectedPeriod, setSelectedPeriod] = useState<TimePeriod>(
|
||||||
|
TIME_PERIODS.find((p) => p.value === "365d") || TIME_PERIODS[3]
|
||||||
|
);
|
||||||
|
|
||||||
// Fetch analytics data
|
// Fetch analytics data
|
||||||
const { data: stats, isLoading: statsLoading } = useQuery({
|
const { data: stats, isLoading: statsLoading } = useQuery({
|
||||||
queryKey: ["transaction-stats"],
|
queryKey: ["transaction-stats", selectedPeriod.days],
|
||||||
queryFn: () => apiClient.getTransactionStats(365), // Last year
|
queryFn: () => apiClient.getTransactionStats(selectedPeriod.days),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: accounts, isLoading: accountsLoading } = useQuery({
|
const { data: accounts, isLoading: accountsLoading } = useQuery({
|
||||||
@@ -27,8 +36,8 @@ function AnalyticsDashboard() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { data: balances, isLoading: balancesLoading } = useQuery({
|
const { data: balances, isLoading: balancesLoading } = useQuery({
|
||||||
queryKey: ["historical-balances"],
|
queryKey: ["historical-balances", selectedPeriod.days],
|
||||||
queryFn: () => apiClient.getHistoricalBalances(365), // Get 1 year of history
|
queryFn: () => apiClient.getHistoricalBalances(selectedPeriod.days),
|
||||||
});
|
});
|
||||||
|
|
||||||
const isLoading = statsLoading || accountsLoading || balancesLoading;
|
const isLoading = statsLoading || accountsLoading || balancesLoading;
|
||||||
@@ -66,6 +75,13 @@ function AnalyticsDashboard() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Time Period Filter */}
|
||||||
|
<TimePeriodFilter
|
||||||
|
selectedPeriod={selectedPeriod}
|
||||||
|
onPeriodChange={setSelectedPeriod}
|
||||||
|
className="bg-white rounded-lg shadow p-4 border border-gray-200"
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Stats Cards */}
|
{/* Stats Cards */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
<StatCard
|
<StatCard
|
||||||
@@ -133,7 +149,7 @@ function AnalyticsDashboard() {
|
|||||||
|
|
||||||
{/* Monthly Trends */}
|
{/* Monthly Trends */}
|
||||||
<div className="bg-white rounded-lg shadow p-6 border border-gray-200">
|
<div className="bg-white rounded-lg shadow p-6 border border-gray-200">
|
||||||
<MonthlyTrends />
|
<MonthlyTrends days={selectedPeriod.days} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Summary Section */}
|
{/* Summary Section */}
|
||||||
|
|||||||
Reference in New Issue
Block a user