Skip to content

Commit

Permalink
Merge pull request #425 from MeetDOD/new-issue-367
Browse files Browse the repository at this point in the history
  • Loading branch information
akbatra567 authored Jul 12, 2024
2 parents 9f43667 + ef95830 commit 4244455
Show file tree
Hide file tree
Showing 9 changed files with 276 additions and 3 deletions.
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 @@ -11,6 +11,7 @@ import Users from "./pages/Users";
import Posts from "./pages/Posts";
import { Toaster } from "react-hot-toast";
import UpdatePost from "./components/UpdatePost";
import Graphs from "./pages/Graphs";
// import axios from "axios";
// axios.defaults.baseURL = "http://localhost:3001/";

Expand Down Expand Up @@ -68,6 +69,14 @@ function App() {
</AuthenticatedRoute>
}
/>
<Route
path="/admin/statistics"
element={
<AuthenticatedRoute>
<Graphs />
</AuthenticatedRoute>
}
/>
<Route path="*" element={<PageNotFound />} />
</Routes>
<Toaster/>
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
49 changes: 49 additions & 0 deletions backend/src/routes/admin/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,55 @@ 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, getGraphsStatsController, updatePostController, deletePostController, getPostByIdController } from './controller';
import { isAdmin } from '../../middleware/adminAuth';

const adminRouter = Router();
Expand All @@ -20,6 +20,8 @@ adminRouter.get("/posts/trending", isAdmin,getAdminTrendingPostsController );

adminRouter.get("/getCardStatus", isAdmin,getAdminStatsController );

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

adminRouter.get('/postbyid/:id', isAdmin,getPostByIdController);

adminRouter.patch('/posts/update/:postId', isAdmin, updatePostController);
Expand Down
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

0 comments on commit 4244455

Please sign in to comment.