rough dashboard components

This commit is contained in:
2026-04-04 19:45:52 +05:30
parent ffa41825dd
commit 84059a84b5
7 changed files with 473 additions and 0 deletions

4
react-openapi/index.ts Normal file
View File

@@ -0,0 +1,4 @@
export { default as Admin } from "./Admin";
export { api, auth, initializeApiClients } from "./api/client";
export { getAppConfig } from "./config";
export type { AppConfig, ResourceConfig, ResourceField } from "./types/config";

140
src/Dashboard.tsx Normal file
View File

@@ -0,0 +1,140 @@
import * as React from "react";
import { Box, Container, Grid, Typography, Avatar } from "@mui/material";
import LatestItemsList, { LatestItem } from "./components/LatestItemsList";
import ProgressCard from "./components/ProgressCard";
import HistoryChart from "./components/HistoryChart";
import ShoppingBagIcon from "@mui/icons-material/ShoppingBag";
import SubscriptionsIcon from "@mui/icons-material/Subscriptions";
import RestaurantIcon from "@mui/icons-material/Restaurant";
import FoodBankIcon from "@mui/icons-material/FoodBank";
const mockLatestItems: LatestItem[] = [
{
id: 1,
icon: <ShoppingBagIcon sx={{ color: "#388e3c" }} />,
iconBgColor: "#e8f5e9",
title: "Grocery Shopping",
subtitle: "Buy some grocery",
amount: "Rs 3000",
timeAgo: "3 days ago",
},
{
id: 2,
icon: <SubscriptionsIcon sx={{ color: "#7b1fa2" }} />,
iconBgColor: "#f3e5f5",
title: "Subscription",
subtitle: "Netflix monthly",
amount: "Rs 800",
timeAgo: "5 days ago",
},
{
id: 3,
icon: <RestaurantIcon sx={{ color: "#d32f2f" }} />,
iconBgColor: "#ffebee",
title: "Food",
subtitle: "Buy a chinese noodles",
amount: "Rs 1000",
timeAgo: "6 days ago",
},
{
id: 4,
icon: <FoodBankIcon sx={{ color: "#fbc02d" }} />,
iconBgColor: "#fff8e1",
title: "Food Club",
subtitle: "Buy a chinese noodles",
amount: "Rs 1000",
timeAgo: "6 days ago",
},
];
const mockChartData = {
today: [
{ id: "6am", amount: 100 },
{ id: "9am", amount: 500 },
{ id: "12pm", amount: 200 },
{ id: "3pm", amount: 1000, highlighted: true },
{ id: "6pm", amount: 600 },
{ id: "9pm", amount: 300 },
],
week: [
{ id: "Mon", amount: 1500 },
{ id: "Tue", amount: 1000 },
{ id: "Wed", amount: 2000 },
{ id: "Thu", amount: 500, highlighted: true },
{ id: "Fri", amount: 3000 },
{ id: "Sat", amount: 4500 },
{ id: "Sun", amount: 2000 },
],
month: [
{ id: "Week 1", amount: 10000 },
{ id: "Week 2", amount: 5000 },
{ id: "Week 3", amount: 12000, highlighted: true },
{ id: "Week 4", amount: 8000 },
],
year: [
{ id: "Q1", amount: 50000 },
{ id: "Q2", amount: 45000 },
{ id: "Q3", amount: 60000, highlighted: true },
{ id: "Q4", amount: 48000 },
],
};
export default function Dashboard() {
return (
<Container maxWidth="lg" sx={{ mt: 4, mb: 4 }}>
<Grid container spacing={4}>
{/* Left Column */}
<Grid item xs={12} md={6}>
{/* User Greeting */}
<Box sx={{ display: 'flex', alignItems: 'center', mb: 4, px: 2 }}>
<Avatar sx={{ width: 56, height: 56, mr: 2 }} src="https://i.pravatar.cc/150?img=5" />
<Box>
<Typography variant="h5" fontWeight={700}>Hello Ananya</Typography>
<Typography variant="subtitle1" color="text.secondary">Good Morning</Typography>
</Box>
</Box>
<ProgressCard
header="Account Balance"
summary="Rs 2,700,00"
progressAmount={270000}
totalAmount={270000}
colorTheme="info"
/>
<Box mt={4}>
<LatestItemsList
title="Recent Transactions"
items={mockLatestItems}
onViewAll={() => {}}
/>
</Box>
</Grid>
{/* Right Column */}
<Grid item xs={12} md={6}>
<Box sx={{ mb: 4, display: 'flex', justifyContent: 'center' }}>
<Typography variant="h6" fontWeight={700}>Statistics</Typography>
</Box>
<ProgressCard
header="Total Expense"
progressAmount={27000}
totalAmount={30000}
summary="Rs 27000 / Rs 30000 per month"
colorTheme="info"
/>
<Box mt={4}>
<HistoryChart
header="Expense Breakdown"
summary="Limit each day Rs 5000/-"
tabs={["Today", "Week", "Month", "Year"]}
data={mockChartData}
/>
</Box>
</Grid>
</Grid>
</Container>
);
}

View File

@@ -99,6 +99,13 @@ export default function Header({
mr: 2, mr: 2,
}} }}
> >
<Button
color="inherit"
onClick={() => navigate("/dashboard")}
sx={{ textTransform: "none", fontWeight: 500 }}
>
Dashboard
</Button>
<Button <Button
color="inherit" color="inherit"
onClick={() => navigate("/admin/profile")} onClick={() => navigate("/admin/profile")}

View File

@@ -0,0 +1,130 @@
import * as React from "react";
import { Box, Typography, ToggleButtonGroup, ToggleButton, Paper } from "@mui/material";
export interface ChartDataPoint {
id: string;
amount: number;
count?: number;
highlighted?: boolean;
}
export interface HistoryChartProps {
header: string;
summary?: string;
tabs: string[];
data: Record<string, ChartDataPoint[]>;
}
export default function HistoryChart({
header,
summary,
tabs,
data,
}: HistoryChartProps) {
const [activeTab, setActiveTab] = React.useState<string>(tabs[0] || "");
const handleTabChange = (_: React.MouseEvent<HTMLElement>, newTab: string | null) => {
if (newTab !== null) {
setActiveTab(newTab);
}
};
const activeDataKey = activeTab.toLowerCase();
const currentData = data[activeDataKey] || data[activeTab] || [];
const maxAmount = Math.max(...currentData.map((d) => d.amount), 1);
return (
<Paper sx={{ p: { xs: 2, sm: 4 }, borderRadius: 4, width: "100%", boxShadow: 'none', border: '1px solid', borderColor: 'divider' }}>
<Typography variant="h6" fontWeight={700} gutterBottom>
{header}
</Typography>
{summary && (
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
{summary}
</Typography>
)}
<ToggleButtonGroup
value={activeTab}
exclusive
onChange={handleTabChange}
fullWidth
sx={{
mb: 4,
bgcolor: (theme) => theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.02)',
borderRadius: 8,
p: 0.5,
"& .MuiToggleButton-root": {
border: "none",
borderRadius: 8,
textTransform: "capitalize",
fontWeight: 600,
color: "text.secondary",
"&.Mui-selected": {
bgcolor: (theme) => theme.palette.mode === 'dark' ? 'primary.dark' : 'primary.light',
color: (theme) => theme.palette.mode === 'dark' ? 'primary.contrastText' : 'primary.main',
boxShadow: '0 2px 8px rgba(0,0,0,0.05)',
},
},
}}
>
{tabs.map((tab) => (
<ToggleButton key={tab} value={tab}>
{tab}
</ToggleButton>
))}
</ToggleButtonGroup>
{/* Chart Area */}
{currentData.length > 0 ? (
<Box sx={{ display: "flex", alignItems: "flex-end", height: 200, mt: 4, position: 'relative' }}>
{currentData.map((point) => {
const heightPerc = (point.amount / maxAmount) * 100;
return (
<Box
key={point.id}
sx={{
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "flex-end",
height: "100%",
}}
>
<Typography variant="caption" sx={{ mb: 1, opacity: 0.7, fontSize: '0.65rem', display: { xs: 'none', sm: 'block' } }}>
{point.amount > 0 ? `Rs ${point.amount}` : ''}
</Typography>
<Box
sx={{
width: "40%",
minWidth: 12,
maxWidth: 32,
height: `${heightPerc}%`,
minHeight: "4px",
bgcolor: point.highlighted ? "error.main" : "grey.300",
borderRadius: 4,
transition: "height 0.5s cubic-bezier(0.4, 0, 0.2, 1)",
...(point.highlighted && {
boxShadow: (theme) => `0 4px 12px ${theme.palette.error.main}40`,
}),
}}
/>
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, fontWeight: 500, fontSize: '0.7rem' }}>
{point.id}
</Typography>
</Box>
);
})}
</Box>
) : (
<Box sx={{ height: 200, display: "flex", alignItems: "center", justifyContent: "center" }}>
<Typography color="text.secondary">No Data Available</Typography>
</Box>
)}
</Paper>
);
}

View File

@@ -0,0 +1,110 @@
import * as React from "react";
import {
List,
ListItem,
ListItemAvatar,
ListItemText,
Avatar,
Typography,
Box,
Button,
} from "@mui/material";
export interface LatestItem {
id: string | number;
icon: React.ReactNode;
iconBgColor?: string;
title: string;
subtitle: string;
amount: string;
timeAgo: string;
}
export interface LatestItemsListProps {
title?: string;
items: LatestItem[];
onViewAll?: () => void;
}
export default function LatestItemsList({
title = "Recent Transactions",
items,
onViewAll,
}: LatestItemsListProps) {
return (
<Box sx={{ width: "100%", bgcolor: "background.paper", borderRadius: 4, p: 2 }}>
{/* Header */}
<Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2, px: 2 }}>
<Typography variant="h6" fontWeight="bold">
{title}
</Typography>
{onViewAll && (
<Button
variant="text"
color="inherit"
size="small"
sx={{ textTransform: "none", color: "text.secondary", fontWeight: "medium" }}
onClick={onViewAll}
>
view all
</Button>
)}
</Box>
{/* List */}
<List disablePadding>
{items.map((item, index) => (
<ListItem
key={item.id}
sx={{
px: { xs: 1, sm: 2 },
py: 2,
mb: index !== items.length - 1 ? 1 : 0,
borderRadius: 3,
"&:hover": { bgcolor: "action.hover" },
transition: "background-color 0.2s ease",
}}
>
<ListItemAvatar>
<Avatar
variant="rounded"
sx={{
bgcolor: item.iconBgColor || "grey.200",
color: "inherit",
width: 48,
height: 48,
borderRadius: 3,
mr: 2,
}}
>
{item.icon}
</Avatar>
</ListItemAvatar>
<ListItemText
primary={
<Typography variant="subtitle1" fontWeight={600} color="text.primary">
{item.title}
</Typography>
}
secondary={
<Typography variant="body2" color="text.secondary">
{item.subtitle}
</Typography>
}
/>
<Box sx={{ textAlign: "right" }}>
<Typography variant="subtitle1" fontWeight={700} color="text.primary">
{item.amount}
</Typography>
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.5 }}>
{item.timeAgo}
</Typography>
</Box>
</ListItem>
))}
</List>
</Box>
);
}

View File

@@ -0,0 +1,80 @@
import * as React from "react";
import { Box, Typography, Paper, LinearProgress, linearProgressClasses } from "@mui/material";
export interface ProgressCardProps {
header: string;
summary?: string;
progressAmount: number;
totalAmount: number;
colorTheme?: "primary" | "secondary" | "error" | "info" | "success" | "warning";
}
export default function ProgressCard({
header,
summary,
progressAmount,
totalAmount,
colorTheme = "info",
}: ProgressCardProps) {
const percentage = Math.min(100, Math.max(0, (progressAmount / totalAmount) * 100)) || 0;
const displaySummary = summary ?? `Rs ${progressAmount} / Rs ${totalAmount}`;
const parts = displaySummary.split('/');
const prefixAmount = parts[0]?.trim() || '';
const suffixString = parts.length > 1 ? `/ ${parts.slice(1).join('/').trim()}` : '';
return (
<Paper
elevation={4}
sx={{
width: "100%",
p: { xs: 3, md: 4 },
borderRadius: 4,
background: (theme) =>
colorTheme === "info"
? "linear-gradient(135deg, #0284c7 0%, #06b6d4 100%)"
: `linear-gradient(135deg, ${theme.palette[colorTheme].main} 0%, ${theme.palette[colorTheme].light} 100%)`,
color: "#fff",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
position: 'relative',
overflow: 'hidden',
boxShadow: (theme) => `0 12px 24px -10px ${theme.palette.mode === 'dark' ? '#000' : theme.palette[colorTheme].main}`,
}}
>
<Typography variant="subtitle1" fontWeight={600} sx={{ opacity: 0.9, mb: 1 }}>
{header}
</Typography>
<Typography variant="h3" fontWeight={800} sx={{ mb: 3 }}>
{prefixAmount}{" "}
{suffixString && (
<Typography component="span" variant="subtitle1" sx={{ opacity: 0.7, fontWeight: 500 }}>
{suffixString}
</Typography>
)}
</Typography>
<Box sx={{ width: "85%" }}>
<LinearProgress
variant="determinate"
value={percentage}
sx={{
height: 10,
borderRadius: 5,
[`&.${linearProgressClasses.colorPrimary}`]: {
backgroundColor: "rgba(0, 0, 0, 0.2)",
},
[`& .${linearProgressClasses.bar}`]: {
borderRadius: 5,
backgroundColor: "#fff",
},
}}
/>
</Box>
</Paper>
);
}

View File

@@ -11,6 +11,7 @@ import {
Toolbar Toolbar
} from "@mui/material"; } from "@mui/material";
import Home from './Home'; import Home from './Home';
import Dashboard from './Dashboard';
import { Admin, initializeApiClients } from '../react-openapi'; import { Admin, initializeApiClients } from '../react-openapi';
import { configuration, profileConfiguration } from './openapi-config'; import { configuration, profileConfiguration } from './openapi-config';
import { Buffer } from 'buffer'; import { Buffer } from 'buffer';
@@ -35,6 +36,7 @@ initializeApiClients(API_BASE, AUTH_BASE);
const routerMapping = [ const routerMapping = [
{ path: "/", component: Home, headerTitle: "Home" }, { path: "/", component: Home, headerTitle: "Home" },
{ path: "/home", component: Home, headerTitle: "Home" }, { path: "/home", component: Home, headerTitle: "Home" },
{ path: "/dashboard", component: Dashboard, headerTitle: "Dashboard" },
{ path: "/admin/*", component: Admin, headerTitle: "Admin" }, { path: "/admin/*", component: Admin, headerTitle: "Admin" },
]; ];