rolling and calender toggle
This commit is contained in:
@@ -36,8 +36,8 @@ export default function Dashboard() {
|
|||||||
income: null
|
income: null
|
||||||
});
|
});
|
||||||
|
|
||||||
const [mode, setMode] =
|
const [mode, setMode] = React.useState<"expense" | "income">("expense");
|
||||||
React.useState<"expense" | "income">("expense");
|
const [period, setPeriod] = React.useState<"rolling" | "calendar">("rolling");
|
||||||
|
|
||||||
const [loading, setLoading] = React.useState(true);
|
const [loading, setLoading] = React.useState(true);
|
||||||
const [error, setError] = React.useState<string | null>(null);
|
const [error, setError] = React.useState<string | null>(null);
|
||||||
@@ -56,8 +56,8 @@ export default function Dashboard() {
|
|||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
fetchLatestTransactions("expense"),
|
fetchLatestTransactions("expense"),
|
||||||
fetchLatestTransactions("income"),
|
fetchLatestTransactions("income"),
|
||||||
fetchAggregatedExpenses(),
|
fetchAggregatedExpenses(period),
|
||||||
fetchAggregatedIncome()
|
fetchAggregatedIncome(period)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
setLatest({
|
setLatest({
|
||||||
@@ -79,7 +79,7 @@ export default function Dashboard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadData();
|
loadData();
|
||||||
}, []);
|
}, [period]);
|
||||||
|
|
||||||
const currentData = aggregated[mode];
|
const currentData = aggregated[mode];
|
||||||
const currentLatest = latest[mode];
|
const currentLatest = latest[mode];
|
||||||
@@ -132,6 +132,8 @@ export default function Dashboard() {
|
|||||||
summary="Interactive chronological tracking"
|
summary="Interactive chronological tracking"
|
||||||
tabs={["Daily", "Weekly", "Monthly"]}
|
tabs={["Daily", "Weekly", "Monthly"]}
|
||||||
data={currentData?.chartData || {}}
|
data={currentData?.chartData || {}}
|
||||||
|
period={period}
|
||||||
|
onPeriodChange={setPeriod}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ export interface HistoryChartProps {
|
|||||||
summary?: string;
|
summary?: string;
|
||||||
tabs: string[];
|
tabs: string[];
|
||||||
data: Record<string, ChartDataPoint[]>;
|
data: Record<string, ChartDataPoint[]>;
|
||||||
|
period: "rolling" | "calendar",
|
||||||
|
onPeriodChange: (mode: "rolling" | "calendar") => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function HistoryChart({
|
export default function HistoryChart({
|
||||||
@@ -20,6 +22,8 @@ export default function HistoryChart({
|
|||||||
summary,
|
summary,
|
||||||
tabs,
|
tabs,
|
||||||
data,
|
data,
|
||||||
|
period,
|
||||||
|
onPeriodChange,
|
||||||
}: HistoryChartProps) {
|
}: HistoryChartProps) {
|
||||||
const [activeTab, setActiveTab] = React.useState<string>(tabs[0] || "");
|
const [activeTab, setActiveTab] = React.useState<string>(tabs[0] || "");
|
||||||
|
|
||||||
@@ -102,6 +106,22 @@ export default function HistoryChart({
|
|||||||
))}
|
))}
|
||||||
</ToggleButtonGroup>
|
</ToggleButtonGroup>
|
||||||
|
|
||||||
|
<ToggleButtonGroup
|
||||||
|
value={period}
|
||||||
|
exclusive
|
||||||
|
onChange={(_, v) => v && onPeriodChange(v)}
|
||||||
|
size="small"
|
||||||
|
sx={{ mb: 2 }}
|
||||||
|
>
|
||||||
|
<ToggleButton value="rolling">Rolling</ToggleButton>
|
||||||
|
<ToggleButton
|
||||||
|
value="calendar"
|
||||||
|
disabled={activeTab.toLowerCase() === "daily"}
|
||||||
|
>
|
||||||
|
Calendar
|
||||||
|
</ToggleButton>
|
||||||
|
</ToggleButtonGroup>
|
||||||
|
|
||||||
{/* Chart Area */}
|
{/* Chart Area */}
|
||||||
{currentData.length > 0 ? (
|
{currentData.length > 0 ? (
|
||||||
<Box sx={{ display: "flex", alignItems: "flex-end", height: 200, mt: 4, position: 'relative' }}>
|
<Box sx={{ display: "flex", alignItems: "flex-end", height: 200, mt: 4, position: 'relative' }}>
|
||||||
|
|||||||
@@ -80,7 +80,8 @@ export interface AggregatedDashboardData {
|
|||||||
// ---------------- AGGREGATION ----------------
|
// ---------------- AGGREGATION ----------------
|
||||||
// ---------------- AGGREGATION ----------------
|
// ---------------- AGGREGATION ----------------
|
||||||
export async function fetchAggregatedData(
|
export async function fetchAggregatedData(
|
||||||
type: "expense" | "income"
|
type: "expense" | "income",
|
||||||
|
mode: "rolling" | "calendar" = "rolling"
|
||||||
): Promise<AggregatedDashboardData> {
|
): Promise<AggregatedDashboardData> {
|
||||||
const res = await api.get("/expenses", { params: { limit: 0 } });
|
const res = await api.get("/expenses", { params: { limit: 0 } });
|
||||||
const all: any[] = res.data?.items || res.data || [];
|
const all: any[] = res.data?.items || res.data || [];
|
||||||
@@ -105,56 +106,76 @@ export async function fetchAggregatedData(
|
|||||||
const weekEnd = endOfDay(new Date(weekStart.getTime() + 6 * 86400000));
|
const weekEnd = endOfDay(new Date(weekStart.getTime() + 6 * 86400000));
|
||||||
|
|
||||||
// ---------------- MONTH (rolling 5 weeks, Mon–Sun aligned) ----------------
|
// ---------------- MONTH (rolling 5 weeks, Mon–Sun aligned) ----------------
|
||||||
const weeklyBuckets: {
|
const weeklyBuckets = [];
|
||||||
label: string;
|
|
||||||
start: Date;
|
|
||||||
end: Date;
|
|
||||||
amount: number;
|
|
||||||
}[] = [];
|
|
||||||
|
|
||||||
const currentWeekStart = getStartOfWeek(now);
|
if (mode === "rolling") {
|
||||||
|
const currentWeekStart = getStartOfWeek(now);
|
||||||
|
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
const start = new Date(currentWeekStart.getTime() - i * 7 * 86400000);
|
const start = new Date(currentWeekStart.getTime() - i * 7 * 86400000);
|
||||||
const end = endOfDay(new Date(start.getTime() + 6 * 86400000));
|
const end = endOfDay(new Date(start.getTime() + 6 * 86400000));
|
||||||
|
|
||||||
weeklyBuckets.push({
|
weeklyBuckets.push({
|
||||||
label: `${format(start)} - ${format(end)}`,
|
label: `${format(start)} - ${format(end)}`,
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
amount: 0
|
amount: 0
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// calendar weeks within current month
|
||||||
|
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||||
|
let cursor = getStartOfWeek(startOfMonth);
|
||||||
|
|
||||||
|
while (cursor <= now) {
|
||||||
|
const start = new Date(cursor);
|
||||||
|
const end = endOfDay(new Date(start.getTime() + 6 * 86400000));
|
||||||
|
|
||||||
|
weeklyBuckets.push({
|
||||||
|
label: `${format(start)} - ${format(end)}`,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
amount: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
cursor = new Date(cursor.getTime() + 7 * 86400000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- YEAR (rolling 12 months) ----------------
|
// ---------------- YEAR (rolling 12 months) ----------------
|
||||||
const monthlyBuckets: {
|
const monthlyBuckets = [];
|
||||||
label: string;
|
|
||||||
start: Date;
|
|
||||||
end: Date;
|
|
||||||
amount: number;
|
|
||||||
}[] = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < 12; i++) {
|
if (mode === "rolling") {
|
||||||
const d = new Date(now);
|
for (let i = 0; i < 12; i++) {
|
||||||
d.setMonth(d.getMonth() - i);
|
const d = new Date(now);
|
||||||
|
d.setMonth(d.getMonth() - i);
|
||||||
|
|
||||||
const start = new Date(d.getFullYear(), d.getMonth(), 1);
|
const start = new Date(d.getFullYear(), d.getMonth(), 1);
|
||||||
|
|
||||||
const end =
|
const end =
|
||||||
i === 0
|
i === 0
|
||||||
? endOfDay(now) // current month → till now
|
? endOfDay(now) // current month → till now
|
||||||
: endOfDay(new Date(d.getFullYear(), d.getMonth() + 1, 0));
|
: endOfDay(new Date(d.getFullYear(), d.getMonth() + 1, 0));
|
||||||
|
monthlyBuckets.push({
|
||||||
|
label: `${d.toLocaleString("default", { month: "short" })}-${String(d.getFullYear()).slice(2)}`,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
amount: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// calendar year (Jan → current month)
|
||||||
|
for (let i = 0; i <= now.getMonth(); i++) {
|
||||||
|
const start = new Date(now.getFullYear(), i, 1);
|
||||||
|
const end = endOfDay(new Date(now.getFullYear(), i + 1, 0));
|
||||||
|
|
||||||
const label = `${d.toLocaleString("default", {
|
monthlyBuckets.push({
|
||||||
month: "short"
|
label: `${start.toLocaleString("default", { month: "short" })}-${String(start.getFullYear()).slice(2)}`,
|
||||||
})}-${String(d.getFullYear()).slice(2)}`;
|
start,
|
||||||
|
end,
|
||||||
monthlyBuckets.push({
|
amount: 0
|
||||||
label,
|
});
|
||||||
start,
|
}
|
||||||
end,
|
|
||||||
amount: 0
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- LOOP ----------------
|
// ---------------- LOOP ----------------
|
||||||
@@ -231,8 +252,12 @@ export async function fetchAggregatedData(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------- EXPORTS ----------------
|
// ---------------- EXPORTS ----------------
|
||||||
export const fetchAggregatedExpenses = () =>
|
export const fetchAggregatedExpenses = (
|
||||||
fetchAggregatedData("expense");
|
mode: "rolling" | "calendar"
|
||||||
|
) =>
|
||||||
|
fetchAggregatedData("expense", mode);
|
||||||
|
|
||||||
export const fetchAggregatedIncome = () =>
|
export const fetchAggregatedIncome = (
|
||||||
fetchAggregatedData("income");
|
mode: "rolling" | "calendar"
|
||||||
|
) =>
|
||||||
|
fetchAggregatedData("income", mode);
|
||||||
Reference in New Issue
Block a user