diff --git a/frontend/src/app/(authenticated)/dashboard/components/mandal-pie-chart.tsx b/frontend/src/app/(authenticated)/dashboard/components/mandal-pie-chart.tsx
new file mode 100644
index 0000000..a6c7dc0
--- /dev/null
+++ b/frontend/src/app/(authenticated)/dashboard/components/mandal-pie-chart.tsx
@@ -0,0 +1,120 @@
+"use client";
+
+import {
+ ChartContainer,
+ ChartTooltip,
+ ChartTooltipContent,
+ type ChartConfig,
+} from "@/components/ui/chart";
+import { Pie, PieChart, Cell } from "recharts";
+import { useLanguage } from "@/i18n/LanguageContext";
+
+export type MandalPieItem = {
+ mandalId: string;
+ mandalName: string;
+ mandalNameTelugu: string;
+ value: number;
+};
+
+interface MandalPieChartProps {
+ title: string;
+ data: MandalPieItem[];
+ labelPrefix: string;
+}
+
+const PIE_COLORS = [
+ "#22d3ee", // cyan
+ "#3b82f6", // blue
+ "#f59e0b", // amber
+ "#ef4444", // red
+ "#a855f7", // purple
+ "#ec4899", // pink
+ "#10b981", // emerald
+ "#f97316", // orange
+ "#6366f1", // indigo
+ "#14b8a6", // teal
+];
+
+export function MandalPieChart({ title, data, labelPrefix }: MandalPieChartProps) {
+ const { lang } = useLanguage();
+
+ // Build config for ChartContainer
+ const config: ChartConfig = {};
+ data.forEach((item, idx) => {
+ config[item.mandalName] = {
+ label: lang === "te" ? item.mandalNameTelugu : item.mandalName,
+ color: PIE_COLORS[idx % PIE_COLORS.length],
+ };
+ });
+
+ // Build pie data
+ const pieData = data.map((item, idx) => ({
+ name: lang === "te" ? item.mandalNameTelugu : item.mandalName,
+ value: item.value,
+ fill: PIE_COLORS[idx % PIE_COLORS.length],
+ }));
+
+ const hasData = pieData.some((d) => d.value > 0);
+
+ return (
+
+
+ {title}
+
+
+ {hasData ? (
+
+
+ (
+ ₹{Number(value).toLocaleString("en-IN")}
+ )}
+ />
+ }
+ />
+
+ {pieData.map((entry, index) => (
+ |
+ ))}
+
+
+
+ ) : (
+
+ —
+
+ )}
+
+ {/* Custom Legend */}
+
+ {data.map((item, idx) => (
+
+
+
+ {lang === "te" ? item.mandalNameTelugu : item.mandalName}
+
+
+ {labelPrefix} ₹{item.value.toLocaleString("en-IN")}
+
+
+ ))}
+
+
+ );
+}
diff --git a/frontend/src/app/(authenticated)/dashboard/page.tsx b/frontend/src/app/(authenticated)/dashboard/page.tsx
index cdf12db..52ba06c 100644
--- a/frontend/src/app/(authenticated)/dashboard/page.tsx
+++ b/frontend/src/app/(authenticated)/dashboard/page.tsx
@@ -2,7 +2,7 @@
import { useState, useEffect } from "react";
import { ContentLayout } from "../../../components/admin-panel/content-layout";
-import { VillageTransactionsChart } from "./components/village-transactions-chart";
+import { MandalPieChart, MandalPieItem } from "./components/mandal-pie-chart";
import { Loader2 } from "lucide-react";
import { useLanguage } from "@/i18n/LanguageContext";
@@ -14,8 +14,18 @@ type VillageSummary = {
totalLoans: number;
};
+type MandalSummary = {
+ mandalId: string;
+ mandalName: string;
+ mandalNameTelugu: string;
+ totalDeposits: number;
+ totalLoans: number;
+ totalLaagodi: number;
+};
+
type DashboardSummaryResponse = {
villages: VillageSummary[];
+ mandals: MandalSummary[];
totals: {
villageCount: number;
memberCount: number;
@@ -26,7 +36,7 @@ type DashboardSummaryResponse = {
export default function DashboardPage() {
const { t } = useLanguage();
- const [data, setData] = useState([]);
+ const [mandals, setMandals] = useState([]);
const [totals, setTotals] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
@@ -49,7 +59,7 @@ export default function DashboardPage() {
throw new Error(json.error?.message || "Failed to load dashboard summary");
}
- setData(json.data.villages);
+ setMandals(json.data.mandals);
setTotals(json.data.totals);
} catch (err) {
setError(err instanceof Error ? err.message : "Unknown error");
@@ -61,10 +71,26 @@ export default function DashboardPage() {
fetchSummary();
}, []);
- const chartData = data.map((village) => ({
- village: village.villageName,
- deposits: village.totalDeposits,
- loans: village.totalLoans,
+ // Prepare pie chart data per category
+ const depositsData: MandalPieItem[] = mandals.map((m) => ({
+ mandalId: m.mandalId,
+ mandalName: m.mandalName,
+ mandalNameTelugu: m.mandalNameTelugu,
+ value: m.totalDeposits,
+ }));
+
+ const loansData: MandalPieItem[] = mandals.map((m) => ({
+ mandalId: m.mandalId,
+ mandalName: m.mandalName,
+ mandalNameTelugu: m.mandalNameTelugu,
+ value: m.totalLoans,
+ }));
+
+ const laagodiData: MandalPieItem[] = mandals.map((m) => ({
+ mandalId: m.mandalId,
+ mandalName: m.mandalName,
+ mandalNameTelugu: m.mandalNameTelugu,
+ value: m.totalLaagodi,
}));
return (
@@ -108,21 +134,24 @@ export default function DashboardPage() {
)}
-
-
-
-
-
- {t.dashboard.chartTitle}
-
-
- {t.dashboard.chartSubtitle}
-
-
-
-
-
-
+ {/* Pie Charts Grid */}
+
+
+
+
+
)}
diff --git a/frontend/src/app/api/dashboard/summary/route.ts b/frontend/src/app/api/dashboard/summary/route.ts
index cb74c4c..2bac42c 100644
--- a/frontend/src/app/api/dashboard/summary/route.ts
+++ b/frontend/src/app/api/dashboard/summary/route.ts
@@ -13,8 +13,18 @@ type VillageSummary = {
totalLoans: number;
};
+type MandalPieSummary = {
+ mandalId: string;
+ mandalName: string;
+ mandalNameTelugu: string;
+ totalDeposits: number;
+ totalLoans: number;
+ totalLaagodi: number;
+};
+
type DashboardSummaryResponse = {
villages: VillageSummary[];
+ mandals: MandalPieSummary[];
totals: {
villageCount: number;
memberCount: number;
@@ -75,8 +85,144 @@ export async function GET(): Promise {
}),
);
+ // Fetch mandals with their Telugu labels for pie charts
+ const mandals = await prisma.mandals.findMany({
+ select: {
+ id: true,
+ label_english: true,
+ villages: {
+ select: {
+ id: true,
+ },
+ },
+ },
+ orderBy: {
+ label_english: "asc",
+ },
+ });
+
+ // Fetch Telugu labels for mandals
+ const mandalTeluguLabels = await prisma.i18n_labels.findMany({
+ where: {
+ entity_table: "mandals",
+ field: "label_telugu",
+ language_code: "te",
+ },
+ });
+
+ const teluguLabelMap = new Map();
+ for (const label of mandalTeluguLabels) {
+ teluguLabelMap.set(label.entity_id, label.text);
+ }
+
+ // Fetch account types to identify SAVINGS, WITHDRAW, LAAGODI
+ const accountTypes = await prisma.account_types.findMany({
+ where: {
+ name: { in: ["SAVINGS", "WITHDRAW", "LAAGODI"] },
+ is_active: true,
+ },
+ select: {
+ id: true,
+ name: true,
+ },
+ });
+
+ const accountTypeMap = new Map();
+ for (const at of accountTypes) {
+ accountTypeMap.set(at.name, at.id);
+ }
+
+ const savingsTypeId = accountTypeMap.get("SAVINGS");
+ const withdrawTypeId = accountTypeMap.get("WITHDRAW");
+ const laagodiTypeId = accountTypeMap.get("LAAGODI");
+
+ // Get accounts by type for efficient filtering
+ const accountsByType = await prisma.accounts.findMany({
+ where: {
+ account_type_id: {
+ in: [savingsTypeId, withdrawTypeId, laagodiTypeId].filter(Boolean) as string[],
+ },
+ },
+ select: {
+ id: true,
+ account_type_id: true,
+ },
+ });
+
+ const savingsAccountIds = accountsByType
+ .filter((a) => a.account_type_id === savingsTypeId)
+ .map((a) => a.id);
+ const withdrawAccountIds = accountsByType
+ .filter((a) => a.account_type_id === withdrawTypeId)
+ .map((a) => a.id);
+ const laagodiAccountIds = accountsByType
+ .filter((a) => a.account_type_id === laagodiTypeId)
+ .map((a) => a.id);
+
+ // Aggregate transactions per mandal per account type
+ const mandalSummaries: MandalPieSummary[] = await Promise.all(
+ mandals.map(async (mandal) => {
+ const villageIds = mandal.villages.map((v) => v.id);
+
+ const [depositsAgg, loansAgg, laagodiAgg] = await Promise.all([
+ // Deposits = credits to SAVINGS accounts in this mandal's villages
+ savingsAccountIds.length > 0
+ ? prisma.transactions.aggregate({
+ _sum: { amount: true },
+ where: {
+ is_archived: false,
+ is_deleted: false,
+ account_id: { in: savingsAccountIds },
+ members: {
+ village_id: { in: villageIds },
+ },
+ },
+ })
+ : Promise.resolve({ _sum: { amount: null } }),
+ // Loans = transactions on WITHDRAW accounts in this mandal's villages
+ withdrawAccountIds.length > 0
+ ? prisma.transactions.aggregate({
+ _sum: { amount: true },
+ where: {
+ is_archived: false,
+ is_deleted: false,
+ account_id: { in: withdrawAccountIds },
+ members: {
+ village_id: { in: villageIds },
+ },
+ },
+ })
+ : Promise.resolve({ _sum: { amount: null } }),
+ // Laagodi = transactions on LAAGODI accounts in this mandal's villages
+ laagodiAccountIds.length > 0
+ ? prisma.transactions.aggregate({
+ _sum: { amount: true },
+ where: {
+ is_archived: false,
+ is_deleted: false,
+ account_id: { in: laagodiAccountIds },
+ members: {
+ village_id: { in: villageIds },
+ },
+ },
+ })
+ : Promise.resolve({ _sum: { amount: null } }),
+ ]);
+
+ return {
+ mandalId: mandal.id,
+ mandalName: mandal.label_english,
+ mandalNameTelugu: teluguLabelMap.get(mandal.id) || mandal.label_english,
+ totalDeposits: depositsAgg._sum.amount ?? 0,
+ totalLoans: loansAgg._sum.amount ?? 0,
+ totalLaagodi: laagodiAgg._sum.amount ?? 0,
+ };
+ }),
+ );
+
const response: DashboardSummaryResponse = {
villages: summaries,
+ mandals: mandalSummaries,
totals: {
villageCount: villages.length,
memberCount: summaries.reduce(
@@ -105,4 +251,3 @@ export async function GET(): Promise {
);
}
}
-
diff --git a/frontend/src/i18n/translations/en.ts b/frontend/src/i18n/translations/en.ts
index 932aee9..3a9c66b 100644
--- a/frontend/src/i18n/translations/en.ts
+++ b/frontend/src/i18n/translations/en.ts
@@ -48,6 +48,12 @@ const en: Translations = {
chartSubtitle: "Total amount of loans and deposits from each village.",
deposits: "Deposits",
loans: "Loans",
+ pieDeposits: "Sangham Total Deposits",
+ pieLoans: "Sangham Total Loans",
+ pieLaagodi: "Sangham Total Laagodi",
+ totalDepositsLabel: "Total Deposits",
+ totalLoansLabel: "Total Loans",
+ totalLaagodiLabel: "Total Laagodi",
},
membersBrowse: {
diff --git a/frontend/src/i18n/translations/te.ts b/frontend/src/i18n/translations/te.ts
index ca296d7..c590895 100644
--- a/frontend/src/i18n/translations/te.ts
+++ b/frontend/src/i18n/translations/te.ts
@@ -13,6 +13,8 @@ export type Translations = {
dashboard: {
title: string; villages: string; members: string; totalDeposits: string; totalLoans: string;
chartTitle: string; chartSubtitle: string; deposits: string; loans: string;
+ pieDeposits: string; pieLoans: string; pieLaagodi: string;
+ totalDepositsLabel: string; totalLoansLabel: string; totalLaagodiLabel: string;
};
membersBrowse: {
title: string; mandal: string; village: string; filterBtn: string; resetBtn: string;
@@ -150,6 +152,12 @@ const te: Translations = {
chartSubtitle: "ప్రతి గ్రామం నుండి మొత్తం రుణాలు మరియు డిపాజిట్లు.",
deposits: "డిపాజిట్లు",
loans: "రుణాలు",
+ pieDeposits: "సంఘం మొత్తం జమలు",
+ pieLoans: "సంఘం మొత్తం అప్పులు",
+ pieLaagodi: "సంఘం మొత్తం లోన్లు",
+ totalDepositsLabel: "మొత్తం జమలు",
+ totalLoansLabel: "మొత్తం అప్పులు",
+ totalLaagodiLabel: "మొత్తం లోన్లు",
},
// Members – Browse