Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/src/external/bcanSatchel/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const updateEndDateFilter = action (
)
export const updateYearFilter = action (
'updateYearFilter',
(yearFilter: number[] | null) => ({yearFilter})
(yearFilter: number[] | []) => ({yearFilter})
)

/**
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/external/bcanSatchel/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface AppState {
startDateFilter: Date | null;
endDateFilter: Date | null;
searchQuery: string;
yearFilter:number[] | null;
yearFilter:number[] | [];
}

// Define initial state
Expand All @@ -26,7 +26,7 @@ const initialState: AppState = {
startDateFilter: null,
endDateFilter: null,
searchQuery: '',
yearFilter: null
yearFilter: []
};

const store = createStore<AppState>('appStore', initialState);
Expand Down
141 changes: 141 additions & 0 deletions frontend/src/main-page/dashboard/Charts/BarYearGrantStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import {
BarChart,
Bar,
XAxis,
YAxis,
Tooltip,
ResponsiveContainer,
LabelList,
} from "recharts";
import { observer } from "mobx-react-lite";
import {
aggregateCountGrantsByYear,
aggregateMoneyGrantsByYear,
YearAmount,
} from "../grantCalculations";
import "../styles/Dashboard.css";
import { Grant } from "../../../../../middle-layer/types/Grant";
import { useState } from "react";

const BarYearGrantStatus = observer(
({ recentYear, grants }: { recentYear: number; grants: Grant[] }) => {
const [checked, setChecked] = useState(true);

// Filtering data for most receny year
const recentData = grants.filter(
(grant) =>
new Date(grant.application_deadline).getFullYear() == recentYear
);

// Formatting data for chart
const data_money = aggregateMoneyGrantsByYear(recentData, "status")
.flatMap((grant: YearAmount) =>
Object.entries(grant.data).map(([key, value]) => ({
name: key,
value,
}))
)
.sort((a, b) => b.value - a.value);

const data_count = aggregateCountGrantsByYear(recentData, "status")
.flatMap((grant: YearAmount) =>
Object.entries(grant.data).map(([key, value]) => ({
name: key,
value,
}))
)
.sort((a, b) => b.value - a.value);

return (
<div className="chart-container">
<div className="flex flex-row w-full justify-between">
<div>
{/* Title */}
<div className="text-lg w-full text-left font-semibold align">
Year Grant Status
</div>
{/* Year */}
<div className="text-sm w-full text-left align">{recentYear}</div>
</div>
{/* Toggle */}
<div className="mt-2">
<label className="inline-flex items-center mb-5 cursor-pointer">
<span className="me-3 text-sm font-medium text-gray-900 dark:text-gray-300">
Count
</span>
<input
type="checkbox"
checked={checked}
onChange={() => setChecked(!checked)}
className="sr-only peer"
style={{display:"none"}}
/>
<div className=" bg-light-orange relative w-9 h-5 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-dark-orange rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border after:rounded-full after:h-4 after:w-4 after:transition-allpeer-checked:bg-blue-600 dark:peer-checked:bg-blue-600"></div>
<span className="ms-3 text-sm font-medium text-gray-900 dark:text-gray-300">
Money
</span>
</label>
</div>
</div>
<ResponsiveContainer width="100%" height={300} min-width={400}>
<BarChart
data={checked ? data_money : data_count}
layout="vertical"
margin={{ top: 10, right: 60, left: 20, bottom: 30 }}
>
<YAxis
axisLine={false}
type="category"
dx={-10}
dataKey="name"
tickLine={false}
/>
<XAxis
type="number"
width="auto"
hide
key={grants.length}
tickFormatter={(value: number) =>
checked ? `$${value / 1000}k` : `${value}`
}
/>
<Bar
type="monotone"
stackId="a"
dataKey="value"
fill="#F58D5C"
strokeWidth={2}
name="Grants"
radius={[15, 15, 15, 15]}
>
<LabelList
dataKey="value"
position="right"
formatter={(label: any) =>
typeof label === "number"
? checked
? `$${label / 1000}k`
: `${label}`
: label
}
/>
</Bar>
<Tooltip
contentStyle={{
borderRadius: "12px",
backgroundColor: "#fff",
border: "1px solid #ccc",
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
}}
formatter={(value: number) =>
checked ? `$${value.toLocaleString()}` : `${value}`
}
/>
</BarChart>
</ResponsiveContainer>
</div>
);
}
);

export default BarYearGrantStatus;
147 changes: 147 additions & 0 deletions frontend/src/main-page/dashboard/Charts/DonutMoneyApplied.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { PieChart, Pie, Tooltip, ResponsiveContainer } from "recharts";
import { observer } from "mobx-react-lite";
import { aggregateMoneyGrantsByYear, YearAmount } from "../grantCalculations";
import "../styles/Dashboard.css";
import { Grant } from "../../../../../middle-layer/types/Grant";
import { getListApplied } from "../../../../../middle-layer/types/Status";

const DonutMoneyApplied = observer(({ grants }: { grants: Grant[] }) => {
// Helper to sum values for given statuses
const sumByStatus = (data: Record<string, number>, statuses: string[]) =>
Object.entries(data)
.filter(([status]) => statuses.includes(status))
.reduce((sum, [, value]) => sum + value, 0);

// Aggregate money by year
const dataMoney = aggregateMoneyGrantsByYear(grants, "status").map(
(grant: YearAmount) => ({
year: grant.year.toString(),
received: sumByStatus(grant.data, getListApplied(true)),
unreceived: sumByStatus(grant.data, getListApplied(false)),
})
);

// Summing values across years
const [sumReceived, sumUnreceived] = dataMoney.reduce(
([sumR, sumU], { received, unreceived }) => [
sumR + received,
sumU + unreceived,
],
[0, 0]
);
const total = sumReceived + sumUnreceived;
const data = [
{ name: "Received", value: sumReceived, fill: "#F8CC16" },
{ name: "Unreceived", value: sumUnreceived, fill: "#F58D5C" },
];

// Creating the label for the slices
const LabelItem = ({
name,
value,
percent,
color,
}: {
name: string;
value: number;
percent: number;
color: string;
}) => {
return (
<div className="w-[100px] ">
<div style={{ fontWeight: 500 }}>{name}</div>
<div
style={{
height: 3,
width: "80%",
backgroundColor: color,
marginTop: 0,
borderRadius: 2,
}}
/>
<div style={{ fontSize: 12, color: "#555", marginTop: 0 }}>
{`${(percent * 100).toFixed(0)}% ($${(value / 1_000_000).toFixed(
2
)}M)`}
</div>
</div>
);
};

return (
<div
className="chart-container"
style={{ display: "flex", flexDirection: "column", alignItems: "center" }}
>
<div className="relative w-full h-full flex flex-col">
{/* Title */}
<div className="text-lg font-semibold relative text-left">
Money Applied For {/* Total Amount */}
<div className="text-2xl font-semibold mt-1 absolute">
{`$${((sumReceived + sumUnreceived) / 1000000).toLocaleString(
"en-us",
{
maximumFractionDigits: 2,
}
)}M`}
</div>
{/* Floating Right Label */}
{sumUnreceived > 0 && (
<div className="absolute top-2 right-2 p-4 mx-10 my-4 z-50 rounded-3xl bg-white bg-opacity-50">
<LabelItem
name="Unreceived"
value={sumUnreceived}
percent={sumUnreceived / total}
color="#F58D5C"
/>
</div>
)}
{/* Floating Left Label */}
{sumReceived > 0 && (
<div className="absolute -bottom-[240px] left-2 p-4 mx-10 my-4 z-50 rounded-3xl bg-white bg-opacity-50">
<LabelItem
name="Received"
value={sumReceived}
percent={sumReceived / total}
color="#F8CC16"
/>
</div>
)}
</div>
</div>
<ResponsiveContainer width="100%" height={250}>
<PieChart
style={{ maxWidth: "1000px", maxHeight: "300px", aspectRatio: 1 }}
>
<Pie
data={data}
startAngle={90}
endAngle={450}
dataKey="value"
nameKey="name"
innerRadius="60%"
outerRadius="80%"
cornerRadius={50}
stroke="#fff"
strokeWidth={2}
label={false}
/>
<Tooltip
formatter={(value: number, name: string) => [
`$${value.toLocaleString()}`,
name,
]}
contentStyle={{
borderRadius: "12px",
backgroundColor: "#fff",
border: "1px solid #ccc",
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
}}
/>
</PieChart>
</ResponsiveContainer>
</div>
);
});

export default DonutMoneyApplied;
77 changes: 77 additions & 0 deletions frontend/src/main-page/dashboard/Charts/GanttYearGrantTimeline.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { observer } from "mobx-react-lite";
import { Grant } from "../../../../../middle-layer/types/Grant";

export const GanttYearGrantTimeline = observer(
({ recentYear, grants }: { recentYear: number; grants: Grant[] }) => {
// Filter grants for the selected year
// const recentData = grants.filter(
// (grant) =>
// new Date(grant.application_deadline).getFullYear() === recentYear
// );

// const data: (string | Date | number | null)[][] = [
// [
// "Task ID",
// "Task Name",
// "Resource ID",
// "Start Date",
// "End Date",
// "Duration",
// "Percent Complete",
// "Dependencies",
// ],
// ...recentData.map((grant) => {
// const deadline = new Date(grant.application_deadline);
// const startDate = new Date(deadline.getFullYear(), deadline.getMonth(), deadline.getDate() - 14);
// const endDate = new Date(deadline.getFullYear(), deadline.getMonth(), deadline.getDate());

// return [
// String(grant.grantId), // Task ID must be string
// `${grant.organization} (${grant.status}) $${grant.amount}`, // Task Name
// null, // Resource ID
// startDate, // Start Date
// endDate, // End Date
// 0, // Duration (null)
// 100, // Percent Complete
// null, // Dependencies
// ];
// }),
// ];

// const options = {
// height: recentData.length * 50 + 50,
// gantt: {
// trackHeight: 30,
// barHeight: 20,
// criticalPathEnabled: false,
// labelStyle: {
// fontName: "Arial",
// fontSize: 12,
// color: "#000",
// },
// palette: [
// {
// color: "#f58d5c", // All bars same color
// dark: "#f58d5c",
// light: "#f58d5c",
// },
// ],
// },
// };

return (
<div className="chart-container h-full">
{/* Title */}
<div className="text-lg w-full text-left font-semibold">
Year Grant Timeline
</div>
{/* Year */}
<div className="text-sm w-full text-left">{recentYear}</div>

<div className="py-4">{grants.length}</div>
</div>
);
}
);

export default GanttYearGrantTimeline;
Loading