Dashboard Refactor: Flow-based Metrics + Unified Data Model #4
@@ -100,14 +100,6 @@ export default function DashboardView({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid key={section.id} size={section.style?.size || 12 as any}>
|
<Grid key={section.id} size={section.style?.size || 12 as any}>
|
||||||
{section.title && !section.isList && (
|
|
||||||
<Box sx={{ mb: 2 }}>
|
|
||||||
<Typography variant="h6" fontWeight={700}>
|
|
||||||
{section.title}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Component
|
<Component
|
||||||
{...section.settings}
|
{...section.settings}
|
||||||
header={section.title}
|
header={section.title}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export interface LatestItem {
|
|||||||
|
|
||||||
export interface LatestItemsViewProps {
|
export interface LatestItemsViewProps {
|
||||||
items: LatestItem[];
|
items: LatestItem[];
|
||||||
|
header: string;
|
||||||
accentColor: string;
|
accentColor: string;
|
||||||
canExpand: boolean;
|
canExpand: boolean;
|
||||||
onExpand: () => void;
|
onExpand: () => void;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import LatestItemsView from "./LatestItems.view";
|
|||||||
type Props = {
|
type Props = {
|
||||||
reportData: ReportData;
|
reportData: ReportData;
|
||||||
flow: "outflows" | "inflows";
|
flow: "outflows" | "inflows";
|
||||||
|
header: string;
|
||||||
selectedPeriodId: string | null;
|
selectedPeriodId: string | null;
|
||||||
selectedGroupKey?: GroupKey | null;
|
selectedGroupKey?: GroupKey | null;
|
||||||
accentColor: string;
|
accentColor: string;
|
||||||
@@ -15,6 +16,7 @@ type Props = {
|
|||||||
export default function LatestItems({
|
export default function LatestItems({
|
||||||
reportData,
|
reportData,
|
||||||
flow,
|
flow,
|
||||||
|
header,
|
||||||
selectedPeriodId,
|
selectedPeriodId,
|
||||||
selectedGroupKey = null,
|
selectedGroupKey = null,
|
||||||
accentColor,
|
accentColor,
|
||||||
@@ -35,6 +37,7 @@ export default function LatestItems({
|
|||||||
return (
|
return (
|
||||||
<LatestItemsView
|
<LatestItemsView
|
||||||
items={visibleItems}
|
items={visibleItems}
|
||||||
|
header={header}
|
||||||
accentColor={accentColor}
|
accentColor={accentColor}
|
||||||
canExpand={canExpand}
|
canExpand={canExpand}
|
||||||
isFetching={isFetching}
|
isFetching={isFetching}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { LatestItemsViewProps } from "./LatestItems.models";
|
|||||||
|
|
||||||
export default function LatestItemsView({
|
export default function LatestItemsView({
|
||||||
items,
|
items,
|
||||||
|
header,
|
||||||
accentColor,
|
accentColor,
|
||||||
canExpand,
|
canExpand,
|
||||||
onExpand,
|
onExpand,
|
||||||
@@ -23,7 +24,7 @@ export default function LatestItemsView({
|
|||||||
<Box sx={{ width: "100%", bgcolor: "background.paper", borderRadius: 4, p: 2, opacity: isFetching ? 0.6 : 1, transition: "opacity 0.3s ease", pointerEvents: isFetching ? "none" : "auto" }}>
|
<Box sx={{ width: "100%", bgcolor: "background.paper", borderRadius: 4, p: 2, opacity: isFetching ? 0.6 : 1, transition: "opacity 0.3s ease", pointerEvents: isFetching ? "none" : "auto" }}>
|
||||||
<Box sx={{ mb: 2, px: 2 }}>
|
<Box sx={{ mb: 2, px: 2 }}>
|
||||||
<Typography variant="h6" fontWeight="bold">
|
<Typography variant="h6" fontWeight="bold">
|
||||||
Recent Transactions
|
{header}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Box } from "@mui/material";
|
import { Box, Paper, Typography } from "@mui/material";
|
||||||
import { ReportData, GroupKey } from "../../features/report";
|
import { ReportData, GroupKey } from "../../features/report";
|
||||||
import ProgressCard from "./ProgressCard";
|
import ProgressCard from "./ProgressCard";
|
||||||
import { extractTopTags } from "./TopTags.adapter";
|
import { extractTopTags } from "./TopTags.adapter";
|
||||||
@@ -7,6 +7,7 @@ import { extractTopTags } from "./TopTags.adapter";
|
|||||||
type Props = {
|
type Props = {
|
||||||
reportData: ReportData;
|
reportData: ReportData;
|
||||||
flow: "outflows" | "inflows";
|
flow: "outflows" | "inflows";
|
||||||
|
header: string;
|
||||||
selectedPeriodId?: string | null;
|
selectedPeriodId?: string | null;
|
||||||
selectedGroupKey?: GroupKey | null;
|
selectedGroupKey?: GroupKey | null;
|
||||||
setSelectedGroupKey?: (key: GroupKey | null) => void;
|
setSelectedGroupKey?: (key: GroupKey | null) => void;
|
||||||
@@ -17,6 +18,7 @@ type Props = {
|
|||||||
export default function TopTags({
|
export default function TopTags({
|
||||||
reportData,
|
reportData,
|
||||||
flow,
|
flow,
|
||||||
|
header,
|
||||||
selectedPeriodId,
|
selectedPeriodId,
|
||||||
selectedGroupKey,
|
selectedGroupKey,
|
||||||
setSelectedGroupKey,
|
setSelectedGroupKey,
|
||||||
@@ -28,37 +30,56 @@ export default function TopTags({
|
|||||||
}, [reportData, flow, selectedPeriodId]);
|
}, [reportData, flow, selectedPeriodId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Paper
|
||||||
sx={{
|
sx={{
|
||||||
display: "grid",
|
p: { xs: 2.5, sm: 4 },
|
||||||
gridTemplateColumns: {
|
borderRadius: 4,
|
||||||
xs: "1fr",
|
width: "100%",
|
||||||
sm: "repeat(2, 1fr)",
|
boxShadow: "none",
|
||||||
md: "repeat(4, 1fr)",
|
border: "1px solid",
|
||||||
},
|
borderColor: "divider",
|
||||||
gap: 2,
|
bgcolor: "background.paper",
|
||||||
|
opacity: isFetching ? 0.6 : 1,
|
||||||
|
transition: "opacity 0.3s ease",
|
||||||
|
pointerEvents: isFetching ? "none" : "auto",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{items.map((item) => {
|
<Typography variant="h6" fontWeight={700} gutterBottom>
|
||||||
const isSelected = selectedGroupKey?.tags?.includes(item.tag);
|
{header}
|
||||||
return (
|
</Typography>
|
||||||
<ProgressCard
|
|
||||||
key={item.tag}
|
<Box
|
||||||
header={item.tag}
|
sx={{
|
||||||
progressAmount={item.amount}
|
display: "grid",
|
||||||
totalAmount={total}
|
gridTemplateColumns: {
|
||||||
compact={compact}
|
xs: "1fr",
|
||||||
colorTheme={flow === "outflows" ? "error" : "success"}
|
sm: "repeat(2, 1fr)",
|
||||||
selected={isSelected}
|
md: "repeat(4, 1fr)",
|
||||||
isFetching={isFetching}
|
},
|
||||||
onClick={() => {
|
gap: 2,
|
||||||
if (setSelectedGroupKey) {
|
}}
|
||||||
setSelectedGroupKey(isSelected ? null : { tags: [item.tag] });
|
>
|
||||||
}
|
{items.map((item) => {
|
||||||
}}
|
const isSelected = selectedGroupKey?.tags?.includes(item.tag);
|
||||||
/>
|
return (
|
||||||
);
|
<ProgressCard
|
||||||
})}
|
key={item.tag}
|
||||||
</Box>
|
header={item.tag}
|
||||||
|
progressAmount={item.amount}
|
||||||
|
totalAmount={total}
|
||||||
|
compact={compact}
|
||||||
|
colorTheme={flow === "outflows" ? "error" : "success"}
|
||||||
|
selected={isSelected}
|
||||||
|
isFetching={isFetching}
|
||||||
|
onClick={() => {
|
||||||
|
if (setSelectedGroupKey) {
|
||||||
|
setSelectedGroupKey(isSelected ? null : { tags: [item.tag] });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export const configuration: DashboardConfig = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "items",
|
id: "items",
|
||||||
|
title: 'Recent Transactions',
|
||||||
component: LatestItems,
|
component: LatestItems,
|
||||||
style: {
|
style: {
|
||||||
size: 12,
|
size: 12,
|
||||||
|
|||||||
Reference in New Issue
Block a user