diff --git a/admin/src/pages/Comments.tsx b/admin/src/pages/Comments.tsx index 3a31be3..2efe0af 100644 --- a/admin/src/pages/Comments.tsx +++ b/admin/src/pages/Comments.tsx @@ -6,6 +6,7 @@ import { tokenState } from "../store/atoms/auth"; import toast from "react-hot-toast"; import { ColorRing } from 'react-loader-spinner'; import { FaComments } from "react-icons/fa"; +import { TbReportAnalytics } from "react-icons/tb"; const Comments = () => { const [posts, setPosts] = useState([]); @@ -47,6 +48,27 @@ const Comments = () => { } }; + const downloadUsersCommentsReport = async () => { + try { + const response = await axios.get('/api/v1/admin/downloaduserscommentsreport', { + headers: { + Authorization: `Bearer ${token}`, + }, + responseType: 'blob', + }); + + const url = window.URL.createObjectURL(new Blob([response.data])); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', 'StyleShare_Comments_Report.pdf'); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } catch (error) { + console.error('Error downloading users comments report:', error); + } + }; + return (
@@ -68,6 +90,7 @@ const Comments = () => { />
: + <>
@@ -123,6 +146,12 @@ const Comments = () => {
+
+ +
+ }
diff --git a/admin/src/pages/ContactMessages.tsx b/admin/src/pages/ContactMessages.tsx index 66fbf30..9c74662 100644 --- a/admin/src/pages/ContactMessages.tsx +++ b/admin/src/pages/ContactMessages.tsx @@ -8,6 +8,7 @@ import 'react-responsive-modal/styles.css'; import '../styles/Model.css' import { ColorRing } from 'react-loader-spinner'; import { MdMessage } from "react-icons/md"; +import { TbReportAnalytics } from "react-icons/tb"; const ContactMessages = () => { const [contactMessages, setContactMessages] = useState([]); @@ -37,6 +38,27 @@ const ContactMessages = () => { fetchMessages(); }, [token]); + const downloadContactMessagesReport = async () => { + try { + const response = await axios.get('/api/v1/admin/downloadcontactmessagereport', { + headers: { + Authorization: `Bearer ${token}`, + }, + responseType: 'blob', + }); + + const url = window.URL.createObjectURL(new Blob([response.data])); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', 'StyleShare_Contact_Messages_Report.pdf'); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } catch (error) { + console.error('Error downloading the Contact Messages report:', error); + } + }; + const handleOpenModal = (message: IContactMessage) => { setSelectedMessage(message); setOpen(true); @@ -68,6 +90,7 @@ const ContactMessages = () => { /> : + <>
@@ -112,6 +135,12 @@ const ContactMessages = () => {
+
+ +
+ } diff --git a/admin/src/pages/Favorites.tsx b/admin/src/pages/Favorites.tsx index 406b134..77baf83 100644 --- a/admin/src/pages/Favorites.tsx +++ b/admin/src/pages/Favorites.tsx @@ -5,6 +5,7 @@ import { tokenState } from "../store/atoms/auth"; import { IFavoritePost } from "../types"; import { ColorRing } from 'react-loader-spinner'; import { RiHeartsFill } from "react-icons/ri"; +import { TbReportAnalytics } from "react-icons/tb"; const Favorites = () => { const [favoritePosts, setFavoritePosts] = useState([]); @@ -32,6 +33,27 @@ const Favorites = () => { fetchFavoritePosts(); }, [token]); + const downloadUsersFavoritesReport = async () => { + try { + const response = await axios.get('/api/v1/admin/downloadusersfavoritesreport', { + headers: { + Authorization: `Bearer ${token}`, + }, + responseType: 'blob', + }); + + const url = window.URL.createObjectURL(new Blob([response.data])); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', 'StyleShare_Favorites_Report.pdf'); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } catch (error) { + console.error('Error downloading users favorites report:', error); + } + }; + return (
@@ -53,6 +75,7 @@ const Favorites = () => { />
: + <>
@@ -82,6 +105,12 @@ const Favorites = () => {
+
+ +
+ }
diff --git a/admin/src/pages/Posts.tsx b/admin/src/pages/Posts.tsx index 5ed5118..f2cdc2d 100644 --- a/admin/src/pages/Posts.tsx +++ b/admin/src/pages/Posts.tsx @@ -7,6 +7,7 @@ import toast from "react-hot-toast"; import { Link } from "react-router-dom"; import { ColorRing } from 'react-loader-spinner'; import { BsFillPostcardFill } from "react-icons/bs"; +import { TbReportAnalytics } from "react-icons/tb"; const Posts = () => { const [posts, setPosts] = useState([]); @@ -48,6 +49,27 @@ const Posts = () => { } }; + const downloadPostsReport = async () => { + try { + const response = await axios.get('/api/v1/admin/downloadpostsreport', { + headers: { + Authorization: `Bearer ${token}`, + }, + responseType: 'blob', + }); + + const url = window.URL.createObjectURL(new Blob([response.data])); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', 'StyleShare_Posts_Report.pdf'); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } catch (error) { + console.error('Error downloading the Posts report:', error); + } + }; + return (
@@ -69,6 +91,7 @@ const Posts = () => { />
: + <>
@@ -107,6 +130,12 @@ const Posts = () => {
+
+ +
+ }
diff --git a/admin/src/pages/Reactions.tsx b/admin/src/pages/Reactions.tsx index 555d081..b142731 100644 --- a/admin/src/pages/Reactions.tsx +++ b/admin/src/pages/Reactions.tsx @@ -5,6 +5,7 @@ import { tokenState } from "../store/atoms/auth"; import { ColorRing } from 'react-loader-spinner'; import { MdAddReaction } from "react-icons/md"; import { IReaction} from "../types"; +import { TbReportAnalytics } from "react-icons/tb"; const Reactions = () => { const [reactions, setReactions] = useState([]); @@ -32,6 +33,27 @@ const Reactions = () => { fetchReactions(); }, [token]); + const downloadUsersReactionsReport = async () => { + try { + const response = await axios.get('/api/v1/admin/downloadusersreactionreport', { + headers: { + Authorization: `Bearer ${token}`, + }, + responseType: 'blob', + }); + + const url = window.URL.createObjectURL(new Blob([response.data])); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', 'StyleShare_Reactions_Report.pdf'); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } catch (error) { + console.error('Error downloading users reactions report:', error); + } + }; + return (
@@ -53,6 +75,7 @@ const Reactions = () => { />
: + <>
@@ -86,6 +109,12 @@ const Reactions = () => {
+
+ +
+ }
diff --git a/admin/src/pages/Users.tsx b/admin/src/pages/Users.tsx index 2f05513..8624fbd 100644 --- a/admin/src/pages/Users.tsx +++ b/admin/src/pages/Users.tsx @@ -6,6 +6,7 @@ import toast from "react-hot-toast"; import { IUser } from "../types"; import { ColorRing } from 'react-loader-spinner'; import { FaUsers } from "react-icons/fa"; +import { TbReportAnalytics } from "react-icons/tb"; const Users = () => { const [allUsers, setAllUsers] = useState([]); @@ -72,6 +73,27 @@ const Users = () => { } }; + const downloadUsersReport = async () => { + try { + const response = await axios.get('/api/v1/admin/downloadusersreport', { + headers: { + Authorization: `Bearer ${token}`, + }, + responseType: 'blob', + }); + + const url = window.URL.createObjectURL(new Blob([response.data])); + const link = document.createElement('a'); + link.href = url; + link.setAttribute('download', 'StyleShare_Users_Report.pdf'); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } catch (error) { + console.error('Error downloading the Users report:', error); + } + }; + return (
@@ -93,6 +115,7 @@ const Users = () => { />
: + <>
@@ -136,6 +159,12 @@ const Users = () => {
+
+ +
+ }
diff --git a/backend/.env.example b/backend/.env.example index 3428104..5c59ad0 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -4,4 +4,4 @@ PORT=3001 # EMAIL_USER=user_email_id # EMAIL_PASS=16char_app_password # SEND_EMAIL=true # Uncomment this to turn on email functionality on development -# API_KEY="your google gemini api key" \ No newline at end of file +# API_KEY="your google gemini api key" diff --git a/backend/src/routes/admin/controller.ts b/backend/src/routes/admin/controller.ts index ce0c02f..d4bfdbc 100644 --- a/backend/src/routes/admin/controller.ts +++ b/backend/src/routes/admin/controller.ts @@ -688,4 +688,404 @@ export const toggleFeedbackVisibility = async (req: Request, res: Response) => { console.error('Error toggling feedback visibility:', error); res.status(500).json({ error: 'An unexpected error occurred!' }); } +}; + +export const downloadUsersReportController = async (req: Request, res: Response) => { + try { + const currentDate = new Date().toLocaleDateString(); + + const users = await prisma.user.findMany({ + select: { + username: true, + email: true, + createdAt: true, + blocked: true, + }, + }); + + const totalUsers = users.length; + + const doc = new PDFDocument(); + let filename = `StyleShare_Users_Report.pdf`; + filename = encodeURIComponent(filename); + + res.setHeader('Content-disposition', `attachment; filename="${filename}"`); + res.setHeader('Content-type', 'application/pdf'); + + doc.pipe(res); + + doc.fontSize(25).text('StyleShare Users Report', { + align: 'center' + }); + + doc.moveDown(); + doc.fontSize(20).text('Overview', { + align: 'center' + }); + doc.moveDown(); + doc.fontSize(15).text(`Date: ${currentDate}`); + doc.moveDown(); + + doc.fontSize(12).text(`Total Users: ${totalUsers}`); + doc.moveDown(); + + doc.fontSize(15).text('User Details:'); + doc.moveDown(); + + users.forEach(user => { + doc.text(`Username: ${user.username}`); + doc.text(`Email: ${user.email}`); + doc.text(`Created At: ${user.createdAt.toLocaleDateString()}`); + doc.text(`Blocked: ${user.blocked ? 'Yes' : 'No'}`); + doc.moveDown(); + }); + + doc.end(); + } catch (error) { + console.error(error); + res.status(500).json({ + error: "An unexpected exception occurred!", + }); + } +}; + +export const downloadPostsReportController = async (req: Request, res: Response) => { + try { + const currentDate = new Date().toLocaleDateString(); + + const posts = await prisma.post.findMany({ + select: { + title: true, + description: true, + createdAt: true, + author: { + select: { + username: true, + email: true, + }, + }, + }, + }); + + const totalPosts = posts.length; + + const doc = new PDFDocument(); + let filename = `StyleShare_Posts_Report.pdf`; + filename = encodeURIComponent(filename); + + res.setHeader('Content-disposition', `attachment; filename="${filename}"`); + res.setHeader('Content-type', 'application/pdf'); + + doc.pipe(res); + + doc.fontSize(25).text('StyleShare Posts Report', { + align: 'center' + }); + + doc.moveDown(); + doc.fontSize(20).text('Overview', { + align: 'center' + }); + doc.moveDown(); + doc.fontSize(15).text(`Date: ${currentDate}`); + doc.moveDown(); + + doc.fontSize(12).text(`Total Posts: ${totalPosts}`); + doc.moveDown(); + + doc.fontSize(15).text('Post Details:'); + doc.moveDown(); + + posts.forEach(post => { + doc.text(`Title: ${post.title}`); + doc.text(`Description: ${post.description}`); + doc.text(`Created At: ${post.createdAt.toLocaleDateString()}`); + doc.text(`Author: ${post.author.username} (${post.author.email})`); + doc.moveDown(); + }); + + doc.end(); + } catch (error) { + console.error(error); + res.status(500).json({ + error: "An unexpected exception occurred!", + }); + } +}; + +export const downloadContactMessagesReportController = async (req: Request, res: Response) => { + try { + const currentDate = new Date().toLocaleDateString(); + + const contactMessages = await prisma.contactMessage.findMany({ + select: { + name: true, + email: true, + subject: true, + message: true, + createdAt: true, + }, + }); + + const totalContactMessages = contactMessages.length; + + const doc = new PDFDocument(); + let filename = `StyleShare_Contact_Messages_Report.pdf`; + filename = encodeURIComponent(filename); + + res.setHeader('Content-disposition', `attachment; filename="${filename}"`); + res.setHeader('Content-type', 'application/pdf'); + + doc.pipe(res); + + doc.fontSize(25).text('StyleShare Contact Messages Report', { + align: 'center' + }); + + doc.moveDown(); + doc.fontSize(20).text('Overview', { + align: 'center' + }); + doc.moveDown(); + doc.fontSize(15).text(`Date: ${currentDate}`); + doc.moveDown(); + + doc.fontSize(12).text(`Total Contact Messages: ${totalContactMessages}`); + doc.moveDown(); + + doc.fontSize(15).text('Contact Message Details:'); + doc.moveDown(); + + contactMessages.forEach(message => { + doc.text(`Name: ${message.name}`); + doc.text(`Email: ${message.email}`); + doc.text(`Subject: ${message.subject}`); + doc.text(`Message: ${message.message}`); + doc.text(`Created At: ${message.createdAt.toLocaleDateString()}`); + doc.moveDown(); + }); + + doc.end(); + } catch (error) { + console.error(error); + res.status(500).json({ + error: "An unexpected exception occurred!", + }); + } +}; + +export const downloadCommentsReportController = async (req: Request, res: Response) => { + try { + const currentDate = new Date().toLocaleDateString(); + + const comments = await prisma.comment.findMany({ + select: { + content: true, + createdAt: true, + user: { + select: { + username: true, + email: true, + }, + }, + post: { + select: { + title: true, + }, + }, + }, + }); + + const totalComments = comments.length; + + const doc = new PDFDocument(); + let filename = `StyleShare_Comments_Report.pdf`; + filename = encodeURIComponent(filename); + + res.setHeader('Content-disposition', `attachment; filename="${filename}"`); + res.setHeader('Content-type', 'application/pdf'); + + doc.pipe(res); + + doc.fontSize(25).text('StyleShare Comments Report', { + align: 'center' + }); + + doc.moveDown(); + doc.fontSize(20).text('Overview', { + align: 'center' + }); + doc.moveDown(); + doc.fontSize(15).text(`Date: ${currentDate}`); + doc.moveDown(); + + doc.fontSize(12).text(`Total Comments: ${totalComments}`); + doc.moveDown(); + + doc.fontSize(15).text('Comment Details:'); + doc.moveDown(); + + comments.forEach(comment => { + doc.text(`Content: ${comment.content}`); + doc.text(`Created At: ${comment.createdAt.toLocaleDateString()}`); + doc.text(`User: ${comment.user.username} (${comment.user.email})`); + doc.text(`Post: ${comment.post.title}`); + doc.moveDown(); + }); + + doc.end(); + } catch (error) { + console.error(error); + res.status(500).json({ + error: "An unexpected exception occurred!", + }); + } +}; + +export const downloadReactionsReportController = async (req: Request, res: Response) => { + try { + const currentDate = new Date().toLocaleDateString(); + + const reactions = await prisma.reaction.findMany({ + select: { + type: true, + createdAt: true, + user: { + select: { + username: true, + email: true, + }, + }, + post: { + select: { + title: true, + description: true, + author: { + select: { + username: true, + email: true, + }, + }, + }, + }, + }, + }); + + const totalReactions = reactions.length; + + const doc = new PDFDocument(); + let filename = `StyleShare_Reactions_Report.pdf`; + filename = encodeURIComponent(filename); + + res.setHeader('Content-disposition', `attachment; filename="${filename}"`); + res.setHeader('Content-type', 'application/pdf'); + + doc.pipe(res); + + doc.fontSize(25).text('StyleShare Reactions Report', { + align: 'center' + }); + + doc.moveDown(); + doc.fontSize(20).text('Overview', { + align: 'center' + }); + doc.moveDown(); + doc.fontSize(15).text(`Date: ${currentDate}`); + doc.moveDown(); + + doc.fontSize(12).text(`Total Reactions: ${totalReactions}`); + doc.moveDown(); + + doc.fontSize(15).text('Reaction Details:'); + doc.moveDown(); + + reactions.forEach(reaction => { + doc.fontSize(12).text(`Type: ${reaction.type}`); + doc.text(`Created At: ${reaction.createdAt.toLocaleDateString()}`); + doc.text(`User: ${reaction.user.username} (${reaction.user.email})`); + doc.text(`Post: ${reaction.post.title}`); + doc.text(`Post Description: ${reaction.post.description}`); + doc.text(`Post Author: ${reaction.post.author.username} (${reaction.post.author.email})`); + doc.moveDown(); + }); + + doc.end(); + } catch (error) { + console.error(error); + res.status(500).json({ + error: "An unexpected exception occurred!", + }); + } +}; + +export const downloadFavoritesReportController = async (req: Request, res: Response) => { + try { + const currentDate = new Date().toLocaleDateString(); + + const favorites = await prisma.favorite.findMany({ + select: { + createdAt: true, + user: { + select: { + username: true, + email: true + } + }, + post: { + select: { + title: true, + description: true + } + } + }, + orderBy: { + createdAt: 'desc' + } + }); + + const totalFavorites = favorites.length; + + const doc = new PDFDocument(); + let filename = `StyleShare_Favorites_Report.pdf`; + filename = encodeURIComponent(filename); + + res.setHeader('Content-disposition', `attachment; filename="${filename}"`); + res.setHeader('Content-type', 'application/pdf'); + + doc.pipe(res); + + doc.fontSize(25).text('StyleShare Favorites Report', { + align: 'center' + }); + + doc.moveDown(); + doc.fontSize(20).text('Overview', { + align: 'center' + }); + doc.moveDown(); + doc.fontSize(15).text(`Date: ${currentDate}`); + doc.moveDown(); + + doc.fontSize(12).text(`Total Favorites: ${totalFavorites}`); + doc.moveDown(); + + doc.fontSize(15).text('Favorite Details:'); + doc.moveDown(); + + favorites.forEach(favorite => { + doc.text(`Created At: ${favorite.createdAt.toLocaleDateString()}`); + doc.text(`User: ${favorite.user.username} (${favorite.user.email})`); + doc.text(`Post: ${favorite.post.title}`); + doc.text(`Post Description: ${favorite.post.description}`); + doc.moveDown(); + }); + + doc.end(); + } catch (error) { + console.error(error); + res.status(500).json({ + error: "An unexpected exception occurred!", + }); + } }; \ No newline at end of file diff --git a/backend/src/routes/admin/route.ts b/backend/src/routes/admin/route.ts index 29fad70..2483c73 100644 --- a/backend/src/routes/admin/route.ts +++ b/backend/src/routes/admin/route.ts @@ -1,5 +1,5 @@ import {Router} from 'express'; -import { getPostReactionsController,getFavoritesController,adminLoginController, adminProfileController, allUserForAdmin, blockUserController, unblockUserController, getAdminPostsController, getAdminTrendingPostsController, getAdminStatsController, getGraphsStatsController, updatePostController, deletePostController, getPostByIdController, getAllContactMessages, deleteCommentController, downloadReportController, getFeedbacks, toggleFeedbackVisibility } from './controller'; +import { getPostReactionsController,getFavoritesController,adminLoginController, adminProfileController, allUserForAdmin, blockUserController, unblockUserController, getAdminPostsController, getAdminTrendingPostsController, getAdminStatsController, getGraphsStatsController, updatePostController, deletePostController, getPostByIdController, getAllContactMessages, deleteCommentController, downloadReportController, getFeedbacks, toggleFeedbackVisibility, downloadCommentsReportController, downloadFavoritesReportController, downloadReactionsReportController, downloadUsersReportController, downloadContactMessagesReportController, downloadPostsReportController } from './controller'; import { isAdmin } from '../../middleware/adminAuth'; const adminRouter = Router(); @@ -38,8 +38,20 @@ adminRouter.get('/favorites', isAdmin, getFavoritesController); adminRouter.get('/downloadReport',isAdmin, downloadReportController); +adminRouter.get('/downloadusersreport',isAdmin, downloadUsersReportController); + +adminRouter.get('/downloadpostsreport',isAdmin, downloadPostsReportController); + +adminRouter.get('/downloadcontactmessagereport',isAdmin, downloadContactMessagesReportController); + adminRouter.get('/getfeedback',isAdmin, getFeedbacks); adminRouter.patch('/toggleFeedbackVisibility/:id', isAdmin, toggleFeedbackVisibility); +adminRouter.get("/downloaduserscommentsreport", downloadCommentsReportController); + +adminRouter.get("/downloadusersfavoritesreport", downloadFavoritesReportController); + +adminRouter.get("/downloadusersreactionreport", downloadReactionsReportController); + export default adminRouter; \ No newline at end of file diff --git a/frontend/src/components/Navbar.tsx b/frontend/src/components/Navbar.tsx index b0f8ce0..f6e2a52 100644 --- a/frontend/src/components/Navbar.tsx +++ b/frontend/src/components/Navbar.tsx @@ -41,15 +41,15 @@ const Navbar: React.FC = ({ theme, toggleTheme }) => { const getNavLinkClass = (path: string) => { return location.pathname === path - ? "block rounded-md px-3 py-2 text-base font-medium text-gray-50 dark:text-gray-50 bg-[#2575fc]" - : "block rounded-md px-3 py-2 text-base font-medium text-gray-50 dark:text-gray-50 hover:bg-[#2575fc] "; + ? "block rounded-md px-3 py-2 text-base font-medium text-gray-50 dark:text-gray-50 bg-[#2575fc] " + : "block rounded-md px-3 py-2 text-base font-medium text-gray-50 dark:text-gray-50 hover:bg-[#2575fc] "; }; return (