Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 367 #428

Closed
wants to merge 6 commits into from
Closed
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
27 changes: 27 additions & 0 deletions admin/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
"dependencies": {
"axios": "^1.7.2",
"dompurify": "^3.1.6",
"chart.js": "^4.4.3",
"react": "^18.3.1",
"react-chartjs-2": "^5.2.0",
"react-dom": "^18.3.1",
"react-hot-toast": "^2.4.1",
"react-icons": "^5.2.1",
Expand Down
9 changes: 9 additions & 0 deletions admin/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Profile from "./pages/Profile";
import Users from "./pages/Users";
import Posts from "./pages/Posts";
import { Toaster } from "react-hot-toast";
import Graphs from "./pages/Graphs";
import UpdatePost from "./components/UpdatePost";
// import axios from "axios";
// axios.defaults.baseURL = "http://localhost:3001/";
Expand Down Expand Up @@ -60,6 +61,14 @@ function App() {
</AuthenticatedRoute>
}
/>
<Route
path="/admin/statistics"
element={
<AuthenticatedRoute>
<Graphs />
</AuthenticatedRoute>
}
/>
<Route
path="/admin/update-post/:postId"
element={
Expand Down
2 changes: 2 additions & 0 deletions admin/src/components/SideBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { CgProfile } from "react-icons/cg";
import { IoNewspaperOutline } from "react-icons/io5";
import logo from '../assets/favicon.png';
import { Link, useLocation } from 'react-router-dom';
import { VscGraphScatter } from "react-icons/vsc";

const SideBar = ({ sidebarOpen, toggleSidebar }: { sidebarOpen: boolean, toggleSidebar: () => void }) => {
const location = useLocation();
Expand All @@ -27,6 +28,7 @@ const SideBar = ({ sidebarOpen, toggleSidebar }: { sidebarOpen: boolean, toggleS
<Link to="/admin/profile" className={linkClasses('/admin/profile')}><CgProfile size={23} className='mr-3'/>My Profile</Link>
<Link to="/admin/users" className={linkClasses('/admin/users')}><HiOutlineUsers size={23} className='mr-3'/>All Users</Link>
<Link to="/admin/posts" className={linkClasses('/admin/posts')}><IoNewspaperOutline size={23} className='mr-3'/>All Posts</Link>
<Link to="/admin/statistics" className={linkClasses('/admin/statistics')}><VscGraphScatter size={23} className='mr-3'/>Statistics</Link>
</nav>
</div>
);
Expand Down
178 changes: 178 additions & 0 deletions admin/src/pages/Graphs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
import { useEffect, useState } from "react";
import Navbar from "../components/Navbar";
import SideBar from "../components/SideBar";
import axios from "axios";
import { Line, Bar } from "react-chartjs-2";
import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, BarElement, ArcElement, Title, Tooltip, Legend } from "chart.js";
import { useRecoilValue } from "recoil";
import { tokenState } from "../store/atoms/auth";

ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, BarElement, ArcElement, Title, Tooltip, Legend);

const Graphs = () => {
const [sidebarOpen, setSidebarOpen] = useState(false);
const [userStats, setUserStats] = useState([]);
const [postStats, setPostStats] = useState([]);
const [commentStats, setCommentStats] = useState([]);
const [favoritesStats, setFavoritesStats] = useState([]);
const [reactionsStats, setReactionsStats] = useState([]);
const [contactMessagesStats, setContactMessagesStats] = useState([]);
const token = useRecoilValue(tokenState);

const toggleSidebar = () => {
setSidebarOpen(!sidebarOpen);
};

useEffect(() => {
const fetchStats = async () => {
try {
const response = await axios.get("/api/v1/admin/getgraphsstatus", {
headers: {
Authorization: `Bearer ${token}`,
},
});
setUserStats(response.data.users);
setPostStats(response.data.posts);
setCommentStats(response.data.comments);
setFavoritesStats(response.data.favorites);
setReactionsStats(response.data.reactions);
setContactMessagesStats(response.data.contacts);
} catch (error) {
console.error("Error fetching stats:", error);
}
};
fetchStats();
}, [token]);

const formatData = (data: any[]) => {
const counts: { [key: string]: number } = {};
data.forEach((item) => {
const date = new Date(item.createdAt);
const formattedDate = `${date.getMonth() + 1}/${date.getDate()}`;
if (!counts[formattedDate]) counts[formattedDate] = 0;
counts[formattedDate]++;
});
const labels = Object.keys(counts);
const values = Object.values(counts);
return { labels, values };
};

const createChartData = ({ data, label, borderColor }:any) => {
return {
labels: data.labels,
datasets: [
{
label: label,
data: data.values,
fill: false,
borderColor: borderColor,
tension: 0.1,
},
],
};
};

const createBarData = ({ data, label, backgroundColor }:any) => {
return {
labels: data.labels,
datasets: [
{
label: label,
data: data.values,
backgroundColor: backgroundColor,
},
],
};
};

const userData = formatData(userStats);
const postData = formatData(postStats);
const commentData = formatData(commentStats);
const favoriteData = formatData(favoritesStats);
const reactionData = formatData(reactionsStats);
const contactMessagesData = formatData(contactMessagesStats);

const userChartData = createChartData({ data: userData, label: "Users registered", borderColor: "rgb(75, 192, 192)" });
const postChartData = createChartData({ data: postData, label: "Posts uploads", borderColor: "rgb(75, 75, 192)" });
const commentChartData = createChartData({ data: commentData, label: "Comments done", borderColor: "rgb(192, 75, 192)" });
const favoriteChartData = createChartData({ data: favoriteData, label: "Favorites done", borderColor: "rgb(192, 75, 75)" });
const reactionChartData = createChartData({ data: reactionData, label: "Reactions done", borderColor: "rgb(75, 192, 75)" });
const contactMessageChartData = createChartData({ data: contactMessagesData, label: "Contact messages received", borderColor: "rgb(192, 192, 75)" });

const userBarData = createBarData({ data: userData, label: "Users registered", backgroundColor: "rgba(75, 192, 192, 0.5)" });
const postBarData = createBarData({ data: postData, label: "Posts uploads", backgroundColor: "rgba(75, 75, 192, 0.5)" });
const commentBarData = createBarData({ data: commentData, label: "Comments done", backgroundColor: "rgba(192, 75, 192, 0.5)" });
const favoriteBarData = createBarData({ data: favoriteData, label: "Favorites done", backgroundColor: "rgba(192, 75, 75, 0.5)" });
const reactionBarData = createBarData({ data: reactionData, label: "Reactions done", backgroundColor: "rgba(75, 192, 75, 0.5)" });
const contactMessageBarData = createBarData({ data: contactMessagesData, label: "Contact messages received", backgroundColor: "rgba(192, 192, 75, 0.5)" });

const Y = new Date();
let year = Y.getFullYear();

return (
<div className="mb-10 w-full">
<Navbar toggleSidebar={toggleSidebar} />
<div className="flex-1 flex flex-col lg:ml-80">
<SideBar sidebarOpen={sidebarOpen} toggleSidebar={toggleSidebar} />
<div className="mx-5 -mt-2 lg:mr-11 overflow-x-auto rounded-xl mb-5">
<h3 className="mb-2 flex font-bold text-xl decoration-sky-500 decoration-dotted underline">Users Over Time {year}</h3>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div className="p-4 bg-white shadow rounded">
<Line data={userChartData} options={{ responsive: true, scales: {x: { title: { display: true, text: 'Date User registered' } }, y: { title: { display: true, text: 'User Count' } },}, maintainAspectRatio: false }} />
</div>
<div className="p-4 bg-white shadow rounded">
<Bar data={userBarData} options={{ responsive: true,scales: {x: { title: { display: true, text: 'Date User registered' } }, y: { title: { display: true, text: 'User Count' } },}, maintainAspectRatio: false }} />
</div>
</div>
<h3 className="mt-5 font-bold text-xl decoration-sky-500 decoration-dotted underline">Posts Over Time {year}</h3>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-2">
<div className="p-4 bg-white shadow rounded">
<Line data={postChartData} options={{ responsive: true, scales: { x: { title: { display: true, text: 'Date when posted' } }, y: { title: { display: true, text: 'Posts Count' } } }, maintainAspectRatio: false }} />
</div>
<div className="p-4 bg-white shadow rounded">
<Bar data={postBarData} options={{ responsive: true,scales: { x: { title: { display: true, text: 'Date when posted' } }, y: { title: { display: true, text: 'Posts Count' } } }, maintainAspectRatio: false }} />
</div>
</div>
<h3 className="mt-5 font-bold text-xl decoration-sky-500 decoration-dotted underline">Comments Over Time {year}</h3>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-2">
<div className="p-4 bg-white shadow rounded">
<Line data={commentChartData} options={{ responsive: true,scales: { x: { title: { display: true, text: 'Date when commented' } }, y: { title: { display: true, text: 'Comments Count' } } }, maintainAspectRatio: false }} />
</div>
<div className="p-4 bg-white shadow rounded">
<Bar data={commentBarData} options={{ responsive: true,scales: { x: { title: { display: true, text: 'Date when commented' } }, y: { title: { display: true, text: 'Comments Count' } } }, maintainAspectRatio: false }} />
</div>
</div>
<h3 className="mt-5 font-bold text-xl decoration-sky-500 decoration-dotted underline">Favorites Over Time {year}</h3>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-2">
<div className="p-4 bg-white shadow rounded">
<Line data={favoriteChartData} options={{ responsive: true,scales: { x: { title: { display: true, text: 'Date' } }, y: { title: { display: true, text: 'Favorite Count' } } }, maintainAspectRatio: false }} />
</div>
<div className="p-4 bg-white shadow rounded">
<Bar data={favoriteBarData} options={{ responsive: true,scales: { x: { title: { display: true, text: 'Date' } }, y: { title: { display: true, text: 'Favorite Count' } } }, maintainAspectRatio: false }} />
</div>
</div>
<h3 className="mt-5 font-bold text-xl decoration-sky-500 decoration-dotted underline">Reactions Over Time {year}</h3>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-2">
<div className="p-4 bg-white shadow rounded">
<Line data={reactionChartData} options={{ responsive: true,scales: { x: { title: { display: true, text: 'Reacted Date' } }, y: { title: { display: true, text: 'Reactions Count' } } }, maintainAspectRatio: false }} />
</div>
<div className="p-4 bg-white shadow rounded">
<Bar data={reactionBarData} options={{ responsive: true,scales: { x: { title: { display: true, text: 'Reacted Date' } }, y: { title: { display: true, text: 'Reactions Count' } } }, maintainAspectRatio: false }} />
</div>
</div>
<h3 className="mt-5 font-bold text-xl decoration-sky-500 decoration-dotted underline">Contact Messages Over Time {year}</h3>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-2">
<div className="p-4 bg-white shadow rounded">
<Line data={contactMessageChartData} options={{ responsive: true,scales: { x: { title: { display: true, text: 'Contacted Date' } }, y: { title: { display: true, text: 'Messages Count' } } }, maintainAspectRatio: false }} />
</div>
<div className="p-4 bg-white shadow rounded">
<Bar data={contactMessageBarData} options={{ responsive: true,scales: { x: { title: { display: true, text: 'Contacted Date' } }, y: { title: { display: true, text: 'Messages Count' } } }, maintainAspectRatio: false }} />
</div>
</div>
</div>
</div>
</div>
);
};

export default Graphs;
4 changes: 3 additions & 1 deletion backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ model Reaction {
postId String @db.ObjectId
user User @relation(fields: [userId], references: [id])
post Post @relation(fields: [postId], references: [id])

createdAt DateTime @default(now())

@@unique([userId, postId])
}

Expand All @@ -87,6 +88,7 @@ model Favorite {
postId String @db.ObjectId
user User @relation("userFavorites", fields: [userId], references: [id])
post Post @relation("postFavorites", fields: [postId], references: [id])
createdAt DateTime @default(now())

@@unique([userId, postId])
}
Expand Down
50 changes: 50 additions & 0 deletions backend/src/routes/admin/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,56 @@ export const getAdminStatsController = async (req: Request, res: Response) => {
}
};

export const getGraphsStatsController = async (req: Request, res: Response) => {
try {
const users = await prisma.user.findMany({
select: {
createdAt: true,
},
});
const posts = await prisma.post.findMany({
select: {
createdAt: true,
},
});
const comments = await prisma.comment.findMany({
select: {
createdAt: true,
},
});
const favorites = await prisma.favorite.findMany({
select: {
createdAt:true
},
});
const contacts = await prisma.contactMessage.findMany({
select: {
createdAt:true
},
});
const reactions = await prisma.reaction.findMany({
select: {
createdAt:true
},
});

res.status(200).json({
users,
posts,
comments,
favorites,
contacts,
reactions
});
} catch (error) {
console.log(error)
res.status(500).json({
error: "An unexpected exception occurred!",
});
}
};


export const getPostByIdController = async (req: Request, res: Response) => {
try {
const postId = req.params.id;
Expand Down
4 changes: 3 additions & 1 deletion backend/src/routes/admin/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Router} from 'express';
import { adminLoginController, adminProfileController, allUserForAdmin, blockUserController, unblockUserController, getAdminPostsController, getAdminTrendingPostsController, getAdminStatsController, updatePostController, deletePostController, getPostByIdController } from './controller';
import { adminLoginController, adminProfileController, allUserForAdmin, blockUserController, unblockUserController, getAdminPostsController, getAdminTrendingPostsController, getAdminStatsController, updatePostController, deletePostController, getPostByIdController, getGraphsStatsController } from './controller';
import { isAdmin } from '../../middleware/adminAuth';

const adminRouter = Router();
Expand All @@ -26,4 +26,6 @@ adminRouter.patch('/posts/update/:postId', isAdmin, updatePostController);

adminRouter.delete('/posts/delete/:postId', isAdmin, deletePostController);

adminRouter.get("/getgraphsstatus", isAdmin,getGraphsStatsController);

export default adminRouter;
4 changes: 3 additions & 1 deletion backend/src/routes/post/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,8 @@ export const favoritePostController = async (req: UserAuthRequest, res: Response
await prisma.favorite.create({
data: {
userId,
postId
postId,
createdAt: new Date()
}
});

Expand Down Expand Up @@ -722,6 +723,7 @@ export const reactToPostController = async (req: UserAuthRequest, res: Response)
userId,
postId,
type,
createdAt: new Date()
},
});
res.status(201).json({ message: "Reaction added", reaction: newReaction });
Expand Down
Loading