diff --git a/app/pufflings/family/[id]/child/[childId]/dashboard/page.tsx b/app/pufflings/family/[id]/child/[childId]/dashboard/page.tsx
index d84986e..9b64244 100644
--- a/app/pufflings/family/[id]/child/[childId]/dashboard/page.tsx
+++ b/app/pufflings/family/[id]/child/[childId]/dashboard/page.tsx
@@ -1,5 +1,6 @@
'use server';
+import FeedChart from '@/app/ui/charts/feedChart';
import DeleteButton from '@/app/ui/deleteButton';
import Recents from '@/app/ui/recents';
import { getChild } from '@/lib/child';
@@ -23,7 +24,7 @@ const Dashboard = async ({
const childInfo = await getChild(parseInt(childId));
return (
- <>
+
);
};
diff --git a/app/ui/charts/feedChart.tsx b/app/ui/charts/feedChart.tsx
new file mode 100644
index 0000000..f567aa9
--- /dev/null
+++ b/app/ui/charts/feedChart.tsx
@@ -0,0 +1,112 @@
+'use client';
+
+import { useEffect, useState } from 'react';
+import { ResponsiveLine, Line } from '@nivo/line';
+import { getFeedChart } from '@/lib/feed';
+import { Radio, RadioGroup } from '@headlessui/react';
+import Loading from '@/app/loading';
+import { Spinner } from '@nextui-org/spinner';
+
+const filterOptions = [
+ { name: 'Last 7 days', value: 7 },
+ { name: 'Last 30 days', value: 30 },
+ { name: 'All-time', value: 100000 },
+];
+
+const FeedChart = ({ childId }: { childId: string }) => {
+ const [isLoading, setIsLoading] = useState(true);
+ const [feedChartData, setFeedChartData] = useState();
+ const [filter, setFilter] = useState(filterOptions[2]);
+
+ useEffect(() => {
+ fetchFeedChart();
+ }, [filter]);
+
+ const getFilterDate = () => {
+ const days = filter.value;
+ const date = new Date();
+ date.setDate(date.getDate() - days);
+ return date.toISOString();
+ };
+
+ const fetchFeedChart = async () => {
+ const feedData = await getFeedChart(childId, getFilterDate());
+ setFeedChartData(feedData as any);
+ setIsLoading(false);
+ };
+
+ const updateTable = async (filter: any) => {
+ setIsLoading(true);
+ setFilter(filter);
+ };
+
+ if (!feedChartData) {
+ return ;
+ }
+
+ return (
+
+
+
+
+
Feed Chart
+
+ {filterOptions.map((option) => (
+
+ {isLoading && option.name == filter.name ? (
+
+ ) : (
+ option.name
+ )}
+
+ ))}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default FeedChart;
diff --git a/lib/feed.ts b/lib/feed.ts
index 2b27482..3bc2c1d 100644
--- a/lib/feed.ts
+++ b/lib/feed.ts
@@ -152,3 +152,31 @@ export const getLastFeed = async (childId: number) => {
take: 1,
});
};
+
+export const getFeedChart = async (childId: string, filter: string) => {
+ const results = await prisma.feed.findMany({
+ where: {
+ child_id: parseInt(childId),
+ start_time: { gte: new Date(filter) },
+ },
+ orderBy: {
+ start_time: 'asc',
+ },
+ select: {
+ start_time: true,
+ amount: true,
+ },
+ });
+
+ return [
+ {
+ id: 'feed',
+ data: results.map((result) => {
+ return {
+ x: result.start_time,
+ y: result.amount?.toNumber(),
+ };
+ }),
+ },
+ ];
+};
diff --git a/package-lock.json b/package-lock.json
index 877f28d..b5d0a47 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,10 +15,12 @@
"@fortawesome/free-regular-svg-icons": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
+ "@headlessui/react": "^2.2.0",
"@heroicons/react": "^2.2.0",
"@nextui-org/dropdown": "^2.1.31",
"@nextui-org/system": "^2.2.6",
"@nextui-org/theme": "^2.2.11",
+ "@nivo/bar": "^0.88.0",
"@nivo/core": "^0.88.0",
"@nivo/line": "^0.88.0",
"@prisma/client": "^5.18.0",
@@ -1031,6 +1033,59 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "1.6.8",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz",
+ "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.8"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.6.12",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.12.tgz",
+ "integrity": "sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.6.0",
+ "@floating-ui/utils": "^0.2.8"
+ }
+ },
+ "node_modules/@floating-ui/react": {
+ "version": "0.26.28",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz",
+ "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.1.2",
+ "@floating-ui/utils": "^0.2.8",
+ "tabbable": "^6.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz",
+ "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.8",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz",
+ "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==",
+ "license": "MIT"
+ },
"node_modules/@formatjs/ecma402-abstract": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.0.0.tgz",
@@ -1139,6 +1194,25 @@
"react": ">=16.3"
}
},
+ "node_modules/@headlessui/react": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.0.tgz",
+ "integrity": "sha512-RzCEg+LXsuI7mHiSomsu/gBJSjpupm6A1qIZ5sWjd7JhARNlMiSA4kKfJpCKwU9tE+zMRterhhrP74PvfJrpXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react": "^0.26.16",
+ "@react-aria/focus": "^3.17.1",
+ "@react-aria/interactions": "^3.21.3",
+ "@tanstack/react-virtual": "^3.8.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^18 || ^19 || ^19.0.0-rc"
+ }
+ },
"node_modules/@heroicons/react": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
@@ -2306,6 +2380,30 @@
"react": ">= 16.14.0 < 19.0.0"
}
},
+ "node_modules/@nivo/bar": {
+ "version": "0.88.0",
+ "resolved": "https://registry.npmjs.org/@nivo/bar/-/bar-0.88.0.tgz",
+ "integrity": "sha512-wckwuHWeCikxGvvdRfGL+dVFsUD9uHk1r9s7bWUfOD+p8BWhxtYqfXpHolEfgGg3UyPaHtpGA7P4zgE5vgo7gQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@nivo/annotations": "0.88.0",
+ "@nivo/axes": "0.88.0",
+ "@nivo/colors": "0.88.0",
+ "@nivo/core": "0.88.0",
+ "@nivo/legends": "0.88.0",
+ "@nivo/scales": "0.88.0",
+ "@nivo/tooltip": "0.88.0",
+ "@react-spring/web": "9.4.5 || ^9.7.2",
+ "@types/d3-scale": "^4.0.8",
+ "@types/d3-shape": "^3.1.6",
+ "d3-scale": "^4.0.2",
+ "d3-shape": "^3.2.0",
+ "lodash": "^4.17.21"
+ },
+ "peerDependencies": {
+ "react": ">= 16.14.0 < 19.0.0"
+ }
+ },
"node_modules/@nivo/colors": {
"version": "0.88.0",
"resolved": "https://registry.npmjs.org/@nivo/colors/-/colors-0.88.0.tgz",
@@ -3208,6 +3306,33 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@tanstack/react-virtual": {
+ "version": "3.11.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.11.1.tgz",
+ "integrity": "sha512-orn2QNe5tF6SqjucHJ6cKTKcRDe3GG7bcYqPNn72Yejj7noECdzgAyRfGt2pGDPemhYim3d1HIR/dgruCnLfUA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/virtual-core": "3.10.9"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@tanstack/virtual-core": {
+ "version": "3.10.9",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.10.9.tgz",
+ "integrity": "sha512-kBknKOKzmeR7lN+vSadaKWXaLS0SZZG+oqpQ/k80Q6g9REn6zRHS/ZYdrIzHnpHgy/eWs00SujveUN/GJT2qTw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
"node_modules/@testing-library/dom": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
@@ -13478,6 +13603,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/tabbable": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
+ "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
+ "license": "MIT"
+ },
"node_modules/tailwind-merge": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-1.14.0.tgz",
diff --git a/package.json b/package.json
index 6ca3fe1..f1893b7 100644
--- a/package.json
+++ b/package.json
@@ -23,10 +23,12 @@
"@fortawesome/free-regular-svg-icons": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
+ "@headlessui/react": "^2.2.0",
"@heroicons/react": "^2.2.0",
"@nextui-org/dropdown": "^2.1.31",
"@nextui-org/system": "^2.2.6",
"@nextui-org/theme": "^2.2.11",
+ "@nivo/bar": "^0.88.0",
"@nivo/core": "^0.88.0",
"@nivo/line": "^0.88.0",
"@prisma/client": "^5.18.0",