diff --git a/client/src/pages/blog/[slug]/index.jsx b/client/src/pages/blog/[slug]/index.jsx index 0307bdb..73507d0 100644 --- a/client/src/pages/blog/[slug]/index.jsx +++ b/client/src/pages/blog/[slug]/index.jsx @@ -178,12 +178,8 @@ const Page = () => { )} } - -

- {postDetails.content} -

+

- : <> diff --git a/client/src/pages/blog/components/card.jsx b/client/src/pages/blog/components/card.jsx index 5f6bd88..4f89515 100644 --- a/client/src/pages/blog/components/card.jsx +++ b/client/src/pages/blog/components/card.jsx @@ -52,7 +52,7 @@ const Card = ({ blogId, title, author, category, time, content, imageFileName })

{author}

  

{time}

-

{content}

+

Read more
-
- Feedback + return ( +
+
+ Feedback +
+ +
- - - -
- ); + ); }; export default LikeDislike; diff --git a/client/src/pages/dashboard/[slug]/index.jsx b/client/src/pages/dashboard/[slug]/index.jsx new file mode 100644 index 0000000..cbd2e18 --- /dev/null +++ b/client/src/pages/dashboard/[slug]/index.jsx @@ -0,0 +1,159 @@ +// IMPORT LIBRARIES +import React, { useState, useEffect } from 'react'; +import { useRouter } from 'next/router'; +import Cookie from 'js-cookie'; + +// IMPORT COMPONENTS +import Layout from '../components/layout'; +import IndivdualBlogPostMetrics from '../components/individualBlogPostMetrics'; +import PieChart from '../components/charts/pieChart'; +import Breadcrumb from '../components/breadcrumb'; +export default function Page() { + const router = useRouter(); + const adminToken = Cookie.get('token') || null; + const [blogPostMetrics, setBlogPostMetrics] = useState(null); + const [blogPostMetricsFetchLoading, setBlogPostMetricsFetchLoading] = useState(true); + const [blogPostMetricsFetchError, setBlogPostMetricsFetchError] = useState(false); + const [blogPostMetricsFetchErrorDetails, setBlogPostMetricsFetchErrorDetails] = useState(null); + + const fetchBlogPostMetrics = async (slug) => { + try { + const response = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_ENDPOINT}/analytics/metrics/${slug}`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${adminToken}` + } + }); + + if (!response.ok) { + const errorDetails = await response.json(); + setBlogPostMetricsFetchErrorDetails({ + statusCode: response.status, + errorMessage: errorDetails?.message || errorDetails || "", + errorDescription: errorDetails?.description || "" + }); + setBlogPostMetricsFetchError(true); + setBlogPostMetricsFetchLoading(false); + } + const data = await response.json(); + setBlogPostMetrics(data); + setBlogPostMetricsFetchLoading(false); + + } catch (error) { + setBlogPostMetricsFetchErrorDetails({ + statusCode: 400, + errorMessage: "Unexpected client error", + errorDescription: "Failed to fetch blog post metrics" + }); + setBlogPostMetricsFetchError(true); + setBlogPostMetricsFetchLoading(false); + } + }; + + useEffect(() => { + if (router.query.slug) { + fetchBlogPostMetrics(router.query.slug); + } + }, [router.query.slug]); + + if (blogPostMetricsFetchLoading) { + return ( +
+
+

Loading blog post metrics...

+
+
+ ); + } + + if (!adminToken) { + router.push('/signin'); + } + + if (blogPostMetricsFetchError) { + return ( +
+

{blogPostMetricsFetchErrorDetails.statusCode}: {blogPostMetricsFetchErrorDetails.errorMessage}

+

{blogPostMetricsFetchErrorDetails.errorDescription}

+ +
+ ); + } + + return ( +
+ {blogPostMetrics ? +
+ + +

Blog Post Metrics

+ + +

Blog Post Metrics Visualization

+
+
+ +
+
+ +
+
+
+ : + <> + } +
+ ); +}; + + +Page.getLayout = function getLayout(page) { + return ( + + {page} + + ) + } \ No newline at end of file diff --git a/client/src/pages/dashboard/components/blogPostMetrics.jsx b/client/src/pages/dashboard/components/blogPostMetrics.jsx index e616252..0f28c13 100644 --- a/client/src/pages/dashboard/components/blogPostMetrics.jsx +++ b/client/src/pages/dashboard/components/blogPostMetrics.jsx @@ -1,73 +1,55 @@ // IMPORT LIBRARIES import React, { useState, useEffect } from 'react'; +import Link from 'next/link'; // IMPORT ICONS import SortButtonIcon from '@/assets/icons/sortButton'; -const BlogPostMetrics = () => { +const BlogPostMetrics = ({ adminToken }) => { const [blogPostMetrics, setBlogPostMetrics] = useState([]); const [sortDirection, setSortDirection] = useState({ key: null, ascending: true }); useEffect(() => { - const blogPostMetrics = [ - { - id: "blog-post-id", - name: "Blog Post Title", - visits: 0, - likes: 0, - dislikes: 0, - source: { - email: 0, - direct: 0, - home: 0 - } - }, - { - id: "blog-post-id", - name: "Blog Post Title", - visits: 5, - likes: 0, - dislikes: 0, - source: { - email: 1, - direct: 4, - home: 0 - } - }, - { - id: "blog-post-id", - name: "Blog Post Title", - visits: 4, - likes: 0, - dislikes: 0, - source: { - email: 0, - direct: 7, - home: 0 + const fetchBlogPostMetrics = async () => { + try { + const response = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_ENDPOINT}/analytics/metrics/bulk`, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${adminToken}` + } + }); + + if (!response.ok) { + throw new Error('Failed to fetch blog post metrics'); } - } - ]; + const data = await response.json(); + setBlogPostMetrics(data); - setBlogPostMetrics(blogPostMetrics); - }, []); + } catch (error) { + console.error(error); + } + }; + fetchBlogPostMetrics(); + }, [adminToken]); const sortBlogPostMetrics = (key, subkey = null) => { let tempBlogPostMetrics = [...blogPostMetrics]; let newSortDirection = { key, ascending: true }; if (key === "source") { - if (sortDirection.key === key && sortDirection.ascending) { - newSortDirection = { key, ascending: false }; - tempBlogPostMetrics.sort((a, b) => a.source[subkey] - b.source[subkey]); + newSortDirection.key = `source-${subkey}`; + if (sortDirection.key === `source-${subkey}` && sortDirection.ascending) { + newSortDirection.ascending = false; + tempBlogPostMetrics.sort((a, b) => (a.source[subkey] ?? 0) - (b.source[subkey] ?? 0)); } else { - tempBlogPostMetrics.sort((a, b) => b.source[subkey] - a.source[subkey]); + tempBlogPostMetrics.sort((a, b) => (b.source[subkey] ?? 0) - (a.source[subkey] ?? 0)); } } else { if (sortDirection.key === key && sortDirection.ascending) { - newSortDirection = { key, ascending: false }; - tempBlogPostMetrics.sort((a, b) => a[key] - b[key]); + newSortDirection.ascending = false; + tempBlogPostMetrics.sort((a, b) => (a[key] ?? 0) - (b[key] ?? 0)); } else { - tempBlogPostMetrics.sort((a, b) => b[key] - a[key]); + tempBlogPostMetrics.sort((a, b) => (b[key] ?? 0) - (a[key] ?? 0)); } } @@ -76,25 +58,25 @@ const BlogPostMetrics = () => { } return ( -
- - +
+
+ - - - - - - - - - - - - - - ) diff --git a/client/src/pages/dashboard/components/breadcrumb.jsx b/client/src/pages/dashboard/components/breadcrumb.jsx new file mode 100644 index 0000000..6d57ff2 --- /dev/null +++ b/client/src/pages/dashboard/components/breadcrumb.jsx @@ -0,0 +1,33 @@ +// IMPORT LIBRARIES +import React from 'react'; + +const Breadcrumb = ({ postTitle }) => { + return ( + + ); +}; + +export default Breadcrumb; diff --git a/client/src/pages/dashboard/components/charts/barChart.jsx b/client/src/pages/dashboard/components/charts/barChart.jsx index eeec4fc..b0f408a 100644 --- a/client/src/pages/dashboard/components/charts/barChart.jsx +++ b/client/src/pages/dashboard/components/charts/barChart.jsx @@ -24,20 +24,31 @@ const generateRandomColor = () => { return '#' + Math.floor(Math.random() * 16777215).toString(16); }; -const BarChart = ({ title, labels, datasets }) => { - console.log(labels, datasets); - +const BarChart = ({ title, labels, datasets, ...props }) => { const options = { responsive: true, plugins: { legend: { position: 'top', + labels: { + font: { + size: 14 + } + } }, title: { display: true, text: title, + font: { + size: 18 + } }, }, + scale: { + ticks: { + precision: 0 + } + } }; const data = { @@ -50,7 +61,7 @@ const BarChart = ({ title, labels, datasets }) => { })) }; - return ; + return ; } export default BarChart; diff --git a/client/src/pages/dashboard/components/charts/pieChart.jsx b/client/src/pages/dashboard/components/charts/pieChart.jsx index d6fe79c..387a2e7 100644 --- a/client/src/pages/dashboard/components/charts/pieChart.jsx +++ b/client/src/pages/dashboard/components/charts/pieChart.jsx @@ -10,53 +10,34 @@ ChartJS.register( ArcElement, Tooltip, Legend ); -const COLORS = ["#333333", -"#4CAF50", -"#2196F3", -"#FF9800", -"#F44336", -"#9C27B0", -"#03A9F4", -"#7F8C8D", -"#FFEB3B", -"#C2C2F0", -]; - - const PieChart = ({ title, labels, datasets }) => { - console.log(labels, datasets); - const options = { responsive: true, plugins: { legend: { position: 'top', + labels: { + font: { + size: 14 + } + } }, title: { display: true, text: title, + font: { + size: 18 + } }, }, }; const data = { labels, - datasets: [ - { - label: 'Likes', - data: datasets ? datasets[0].data : [], - backgroundColor: COLORS.slice(0, labels?.length), - borderColor: 'rgb(54, 162, 235)' - } - ], + datasets }; - return ( -
- -
- ); - + return } export default PieChart; diff --git a/client/src/pages/dashboard/components/individualBlogPostMetrics.jsx b/client/src/pages/dashboard/components/individualBlogPostMetrics.jsx new file mode 100644 index 0000000..62f4c8e --- /dev/null +++ b/client/src/pages/dashboard/components/individualBlogPostMetrics.jsx @@ -0,0 +1,77 @@ +// IMPORT LIBRARIES +import React from 'react'; + +const IndivdualBlogPostMetrics = ({ blogPostMetrics }) => { + return ( +
+
+ Blog Title -
+
+
Number of Views
sortBlogPostMetrics("visits")} + onClick={() => sortBlogPostMetrics("uniqueVisit")} >
-
+
+
Source - Email
sortBlogPostMetrics("source", "email")} @@ -103,8 +85,8 @@ const BlogPostMetrics = () => {
-
+
+
Source - Direct
sortBlogPostMetrics("source", "direct")} @@ -113,8 +95,8 @@ const BlogPostMetrics = () => {
-
+
+
Source - Home
sortBlogPostMetrics("source", "home")} @@ -123,8 +105,8 @@ const BlogPostMetrics = () => {
-
+
+
Number of Likes
sortBlogPostMetrics("likes")} @@ -133,8 +115,8 @@ const BlogPostMetrics = () => {
-
+
+
Number of dislikes
sortBlogPostMetrics("dislikes")} @@ -150,26 +132,28 @@ const BlogPostMetrics = () => { {blogPostMetrics.map((item, idx) => { return (
- {item.name} + + +

{item.blogName}

+
- {item.visits} + + {item.uniqueVisit || 0} - {item.source.email} + + {item.source.email || 0} - {item.source.direct} + + {item.source.direct || 0} - {item.source.home} + + {item.source.home || 0} - {item.likes} + + {item.likes || 0} - {item.dislikes} + + {item.dislikes || 0}
+ + + + + + + + + + + + + + + + + + + + + + + +
+ Blog Title + +
+ Number of Views +
+
+
+ Source - Email +
+
+
+ Source - Direct +
+
+
+ Source - Home +
+
+
+ Number of Likes +
+
+
+ Number of dislikes +
+
+

{blogPostMetrics.blogName}

+
+ {blogPostMetrics.uniqueVisit || 0} + + {blogPostMetrics.source && blogPostMetrics.source.email ? blogPostMetrics.source.email : 0} + + {blogPostMetrics.source && blogPostMetrics.source.direct ? blogPostMetrics.source.direct : 0} + + {blogPostMetrics.source && blogPostMetrics.source.home ? blogPostMetrics.source.home : 0} + + {blogPostMetrics.likes || 0} + + {blogPostMetrics.dislikes || 0} +
+
+ + ); +}; + +export default IndivdualBlogPostMetrics; diff --git a/client/src/pages/dashboard/index.jsx b/client/src/pages/dashboard/index.jsx index 2d1e547..37b7e89 100644 --- a/client/src/pages/dashboard/index.jsx +++ b/client/src/pages/dashboard/index.jsx @@ -6,7 +6,6 @@ import Cookie from 'js-cookie'; // IMPORT COMPONENTS import Layout from './components/layout'; import BarChart from './components/charts/barChart'; -import PieChart from './components/charts/pieChart'; import BlogPostMetrics from './components/blogPostMetrics'; export default function Page() { @@ -99,9 +98,9 @@ export default function Page() { {dashboardMetrics && dashboardMetrics.uniqueVisit?.blog ?

Blog Post Metrics

- + -

Unique visits to each blog page

+

Blog Post Metrics Visualization

post?.blogId)} @@ -113,6 +112,7 @@ export default function Page() { } ]} /> +
post?.blogId)} @@ -129,6 +129,7 @@ export default function Page() { } ]} /> +
post?.blogId)} @@ -149,8 +150,11 @@ export default function Page() { borderColor: 'rgb(54, 162, 235)' } ]} + className="mb-8" /> -
+
+ {/* ADDED THIS AS BAR CHART ABOVE */} + {/*

Likes for each blog page

post?.count) }]} />
-
-
+
*/} + +

Category Wise Metrics Visualization

+ cat?.blogId)} + datasets={[ + { + label: "Unique Visits", data: dashboardMetrics?.uniqueVisit?.category?.map(cat => cat?.count), + backgroundColor: 'rgba(54, 162, 235, 0.3)', + borderColor: 'rgb(54, 162, 235)' + } + ]} + className="mb-8" + /> + {/* ADDED THIS AS BAR CHART ABOVE */} + {/*

Category wise Views

cat?.blogId)} datasets={[{ label: "Views", data: dashboardMetrics?.uniqueVisit?.category?.map(cat => cat?.count) }]} /> -
+
*/}
: <> diff --git a/server/src/assets/images/blog/caps-building-communication.png b/server/src/assets/images/blog/caps-building-communication.png new file mode 100644 index 0000000..6527bf0 Binary files /dev/null and b/server/src/assets/images/blog/caps-building-communication.png differ diff --git a/server/src/assets/images/blog/caps-national-sleep-awareness.png b/server/src/assets/images/blog/caps-national-sleep-awareness.png new file mode 100644 index 0000000..688a517 Binary files /dev/null and b/server/src/assets/images/blog/caps-national-sleep-awareness.png differ diff --git a/server/src/assets/images/blog/caps-navigating-winter-break.png b/server/src/assets/images/blog/caps-navigating-winter-break.png new file mode 100644 index 0000000..5ec3922 Binary files /dev/null and b/server/src/assets/images/blog/caps-navigating-winter-break.png differ diff --git a/server/src/assets/images/blog/covid-testing.png b/server/src/assets/images/blog/covid-testing.png new file mode 100644 index 0000000..61e5c6e Binary files /dev/null and b/server/src/assets/images/blog/covid-testing.png differ diff --git a/server/src/assets/images/blog/covid-vaccines-available.png b/server/src/assets/images/blog/covid-vaccines-available.png new file mode 100644 index 0000000..66fdf2e Binary files /dev/null and b/server/src/assets/images/blog/covid-vaccines-available.png differ diff --git a/server/src/assets/images/blog/diet-time-breakfast.png b/server/src/assets/images/blog/diet-time-breakfast.png new file mode 100644 index 0000000..6b5d78c Binary files /dev/null and b/server/src/assets/images/blog/diet-time-breakfast.png differ diff --git a/server/src/assets/images/blog/eat-with-love.png b/server/src/assets/images/blog/eat-with-love.png new file mode 100644 index 0000000..c6bc743 Binary files /dev/null and b/server/src/assets/images/blog/eat-with-love.png differ