From dd5e2cbef8e374dc8ac2b2421d7b1fae3b945092 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Sun, 25 Aug 2024 18:15:11 -0400 Subject: [PATCH 01/37] started implementing analytics dashboard --- frontend/graphql/queries/analyticsQueries.ts | 116 ++++++++++++++++++ frontend/package-lock.json | 32 +++-- frontend/package.json | 3 +- frontend/pages/Admin.tsx | 4 +- frontend/pages/Dashboard.tsx | 51 ++++++++ .../src/components/admin/DashboardButton.tsx | 16 +++ frontend/styles/Dashboard.module.css | 0 7 files changed, 212 insertions(+), 10 deletions(-) create mode 100644 frontend/pages/Dashboard.tsx create mode 100644 frontend/src/components/admin/DashboardButton.tsx create mode 100644 frontend/styles/Dashboard.module.css diff --git a/frontend/graphql/queries/analyticsQueries.ts b/frontend/graphql/queries/analyticsQueries.ts index c891cb0..f77103c 100644 --- a/frontend/graphql/queries/analyticsQueries.ts +++ b/frontend/graphql/queries/analyticsQueries.ts @@ -294,3 +294,119 @@ export const GET_CLICKS_CUSTOM_ANALYTICS = gql` } } `; + +export const GET_ANALYTICS_DASHBOARD_DATA = gql` + query GetAnalyticsDashboardData($startDate: Date!, $endDate: Date!) { + jobClicksDaily: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "daily", clickType: "job") { + date + count + } + jobClicksWeekly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "weekly", clickType: "job") { + date + count + } + jobClicksMonthly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "monthly", clickType: "job") { + date + count + } + jobClicksYearly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "yearly", clickType: "job") { + date + count + } + applyClicksDaily: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "daily", clickType: "apply") { + date + count + } + applyClicksWeekly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "weekly", clickType: "apply") { + date + count + } + applyClicksMonthly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "monthly", clickType: "apply") { + date + count + } + applyClicksYearly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "yearly", clickType: "apply") { + date + count + } + employerClicksDaily: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "daily", clickType: "employer") { + date + count + } + employerClicksWeekly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "weekly", clickType: "employer") { + date + count + } + employerClicksMonthly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "monthly", clickType: "employer") { + date + count + } + employerClicksYearly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "yearly", clickType: "employer") { + date + count + } + } +`; + +export const GET_DASHBOARD_JOB_CLICKS = gql` + query GetDashboardJobClicks($startDate: Date!, $endDate: Date!) { + daily: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "daily", clickType: "job") { + date + count + } + weekly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "weekly", clickType: "job") { + date + count + } + jobClicksMonthly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "monthly", clickType: "job") { + date + count + } + jobClicksYearly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "yearly", clickType: "job") { + date + count + } + } +`; + +export const GET_DASHBOARD_APPLY_CLICKS = gql` + query GetDashboardApplyClicks($startDate: Date!, $endDate: Date!) { + daily: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "daily", clickType: "apply") { + date + count + } + weekly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "weekly", clickType: "apply") { + date + count + } + monthly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "monthly", clickType: "apply") { + date + count + } + yearly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "yearly", clickType: "apply") { + date + count + } + } +`; + +export const GET_DASHBOARD_EMPLOYER_CLICKS = gql` + query GetDashboardEmployerClicks($startDate: Date!, $endDate: Date!) { + daily: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "daily", clickType: "employer") { + date + count + } + weekly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "weekly", clickType: "employer") { + date + count + } + monthly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "monthly", clickType: "employer") { + date + count + } + yearly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "yearly", clickType: "employer") { + date + count + } + } +`; \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 951fc43..4f97330 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -41,7 +41,7 @@ "@types/file-saver": "^2.0.7", "@vercel/analytics": "^1.0.1", "bcrypt": "^5.1.0", - "chart.js": "^4.3.0", + "chart.js": "^4.4.4", "dayjs": "^1.11.6", "dotenv": "^16.0.3", "embla-carousel-react": "^7.0.5", @@ -59,6 +59,7 @@ "react": "18.2.0", "react-chartjs-2": "^5.2.0", "react-dom": "18.2.0", + "react-google-charts": "^4.0.1", "react-hook-form": "^7.41.3", "react-icons": "^4.7.1", "react-responsive": "^9.0.2", @@ -2276,14 +2277,14 @@ } }, "node_modules/chart.js": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.1.tgz", - "integrity": "sha512-QHuISG3hTJ0ftq0I0f5jqH9mNVO9bqG8P+zvMOVslgKajQVvFEX7QAhYNJ+QEmw+uYTwo8XpTimaB82oeTWjxw==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.4.tgz", + "integrity": "sha512-emICKGBABnxhMjUjlYRR12PmOXhJ2eJjEHL2/dZlWjxRAZT1D8xplLFq5M0tMQK8ja+wBS/tuVEJB5C6r7VxJA==", "dependencies": { "@kurkle/color": "^0.3.0" }, "engines": { - "pnpm": ">=7" + "pnpm": ">=8" } }, "node_modules/chownr": { @@ -5951,6 +5952,15 @@ "react": ">= 16.8 || 18.0.0" } }, + "node_modules/react-google-charts": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-google-charts/-/react-google-charts-4.0.1.tgz", + "integrity": "sha512-V/hcMcNuBgD5w49BYTUDye+bUKaPmsU5vy/9W/Nj2xEeGn+6/AuH9IvBkbDcNBsY00cV9OeexdmgfI5RFHgsXQ==", + "peerDependencies": { + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + } + }, "node_modules/react-hook-form": { "version": "7.45.2", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.45.2.tgz", @@ -8962,9 +8972,9 @@ } }, "chart.js": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.3.1.tgz", - "integrity": "sha512-QHuISG3hTJ0ftq0I0f5jqH9mNVO9bqG8P+zvMOVslgKajQVvFEX7QAhYNJ+QEmw+uYTwo8XpTimaB82oeTWjxw==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.4.tgz", + "integrity": "sha512-emICKGBABnxhMjUjlYRR12PmOXhJ2eJjEHL2/dZlWjxRAZT1D8xplLFq5M0tMQK8ja+wBS/tuVEJB5C6r7VxJA==", "requires": { "@kurkle/color": "^0.3.0" } @@ -11655,6 +11665,12 @@ "prop-types": "^15.8.1" } }, + "react-google-charts": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-google-charts/-/react-google-charts-4.0.1.tgz", + "integrity": "sha512-V/hcMcNuBgD5w49BYTUDye+bUKaPmsU5vy/9W/Nj2xEeGn+6/AuH9IvBkbDcNBsY00cV9OeexdmgfI5RFHgsXQ==", + "requires": {} + }, "react-hook-form": { "version": "7.45.2", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.45.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 48b9f22..99e0e22 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -42,7 +42,7 @@ "@types/file-saver": "^2.0.7", "@vercel/analytics": "^1.0.1", "bcrypt": "^5.1.0", - "chart.js": "^4.3.0", + "chart.js": "^4.4.4", "dayjs": "^1.11.6", "dotenv": "^16.0.3", "embla-carousel-react": "^7.0.5", @@ -60,6 +60,7 @@ "react": "18.2.0", "react-chartjs-2": "^5.2.0", "react-dom": "18.2.0", + "react-google-charts": "^4.0.1", "react-hook-form": "^7.41.3", "react-icons": "^4.7.1", "react-responsive": "^9.0.2", diff --git a/frontend/pages/Admin.tsx b/frontend/pages/Admin.tsx index babb739..826b049 100644 --- a/frontend/pages/Admin.tsx +++ b/frontend/pages/Admin.tsx @@ -14,6 +14,7 @@ import EditJobButton from "../src/components/admin/EditJobButton"; import { Analytics } from "@vercel/analytics/react"; import AnalyticsPage from "./Analytics"; import AnalyticsButton from "../src/components/admin/AnalyticsButton"; +import DashboardButton from "../src/components/admin/DashboardButton"; // To ensure unauthenticated people don't access @@ -75,7 +76,8 @@ export default function Admin() {

Analytics

- + +
diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx new file mode 100644 index 0000000..2c1cc7d --- /dev/null +++ b/frontend/pages/Dashboard.tsx @@ -0,0 +1,51 @@ +import { useQuery } from '@apollo/client' +import { + GET_DASHBOARD_APPLY_CLICKS, + GET_DASHBOARD_EMPLOYER_CLICKS, + GET_DASHBOARD_JOB_CLICKS, +} from '../graphql/queries/analyticsQueries' +import { Chart } from 'react-google-charts' +import styles from '../styles/Dashboard.module.css' +import Navbar from '../src/components/general/Navbar' + +function Dashboard() { + const { + data: jobClicks, + loading: jobClicksLoading, + error: jobClicksError, + } = useQuery(GET_DASHBOARD_JOB_CLICKS, { + variables: { startDate: '2024-01-01', endDate: '2024-08-01' }, + }) + const { + data: applyClicks, + loading: applyClicksLoading, + error: applyClicksError, + } = useQuery(GET_DASHBOARD_APPLY_CLICKS, { + variables: { startDate: '2024-01-01', endDate: '2024-08-01' }, + }) + const { + data: employerClicks, + loading: employerClicksLoading, + error: employerClicksError, + } = useQuery(GET_DASHBOARD_EMPLOYER_CLICKS, { + variables: { startDate: '2024-01-01', endDate: '2024-08-01' }, + }) + + if (jobClicksLoading || applyClicksLoading || employerClicksLoading) return
loading
+ + const dailyClickCounts = [['Date', 'Daily Count'], ...jobClicks.daily.map((item: any) => [item.date, item.count])] + const dailyChartOptions = { + title: 'Daily Clicks', + } + + return ( + <> + +
+ +
+ + ) +} + +export default Dashboard diff --git a/frontend/src/components/admin/DashboardButton.tsx b/frontend/src/components/admin/DashboardButton.tsx new file mode 100644 index 0000000..4633b69 --- /dev/null +++ b/frontend/src/components/admin/DashboardButton.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import styles from "../../../styles/components/AdminPageButtons.module.css"; +import Link from "next/link"; + + +export default function DashboardButton(props:any) { + const { text, link } = props; + return ( +
+ + + +
+ ) + +} \ No newline at end of file diff --git a/frontend/styles/Dashboard.module.css b/frontend/styles/Dashboard.module.css new file mode 100644 index 0000000..e69de29 From 470daab1f7bfec5ab943ecdf44feb0fc8f099140 Mon Sep 17 00:00:00 2001 From: ColePurboo Date: Sun, 25 Aug 2024 19:03:25 -0400 Subject: [PATCH 02/37] boom --- .../graphql/resolvers/analytics.resolver.ts | 2 + frontend/graphql/queries/analyticsQueries.ts | 63 ------------------ frontend/pages/Dashboard.tsx | 65 +++++++++---------- 3 files changed, 31 insertions(+), 99 deletions(-) diff --git a/backend/src/graphql/resolvers/analytics.resolver.ts b/backend/src/graphql/resolvers/analytics.resolver.ts index 381659d..f3df0d4 100644 --- a/backend/src/graphql/resolvers/analytics.resolver.ts +++ b/backend/src/graphql/resolvers/analytics.resolver.ts @@ -1077,6 +1077,8 @@ const analyticsResolver = { } catch (err) { console.error("Error executing query:", err); throw new Error("Failed to get custom analytics"); + } finally { + client.release(); } }, }, diff --git a/frontend/graphql/queries/analyticsQueries.ts b/frontend/graphql/queries/analyticsQueries.ts index f77103c..e6c5a5c 100644 --- a/frontend/graphql/queries/analyticsQueries.ts +++ b/frontend/graphql/queries/analyticsQueries.ts @@ -346,67 +346,4 @@ export const GET_ANALYTICS_DASHBOARD_DATA = gql` count } } -`; - -export const GET_DASHBOARD_JOB_CLICKS = gql` - query GetDashboardJobClicks($startDate: Date!, $endDate: Date!) { - daily: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "daily", clickType: "job") { - date - count - } - weekly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "weekly", clickType: "job") { - date - count - } - jobClicksMonthly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "monthly", clickType: "job") { - date - count - } - jobClicksYearly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "yearly", clickType: "job") { - date - count - } - } -`; - -export const GET_DASHBOARD_APPLY_CLICKS = gql` - query GetDashboardApplyClicks($startDate: Date!, $endDate: Date!) { - daily: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "daily", clickType: "apply") { - date - count - } - weekly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "weekly", clickType: "apply") { - date - count - } - monthly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "monthly", clickType: "apply") { - date - count - } - yearly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "yearly", clickType: "apply") { - date - count - } - } -`; - -export const GET_DASHBOARD_EMPLOYER_CLICKS = gql` - query GetDashboardEmployerClicks($startDate: Date!, $endDate: Date!) { - daily: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "daily", clickType: "employer") { - date - count - } - weekly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "weekly", clickType: "employer") { - date - count - } - monthly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "monthly", clickType: "employer") { - date - count - } - yearly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "yearly", clickType: "employer") { - date - count - } - } `; \ No newline at end of file diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index 2c1cc7d..d3855bf 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -1,51 +1,44 @@ -import { useQuery } from '@apollo/client' +import { useQuery } from "@apollo/client"; import { - GET_DASHBOARD_APPLY_CLICKS, - GET_DASHBOARD_EMPLOYER_CLICKS, - GET_DASHBOARD_JOB_CLICKS, -} from '../graphql/queries/analyticsQueries' -import { Chart } from 'react-google-charts' -import styles from '../styles/Dashboard.module.css' -import Navbar from '../src/components/general/Navbar' + GET_ANALYTICS_DASHBOARD_DATA +} from "../graphql/queries/analyticsQueries"; +import { Chart } from "react-google-charts"; +import styles from "../styles/Dashboard.module.css"; +import Navbar from "../src/components/general/Navbar"; function Dashboard() { - const { - data: jobClicks, - loading: jobClicksLoading, - error: jobClicksError, - } = useQuery(GET_DASHBOARD_JOB_CLICKS, { - variables: { startDate: '2024-01-01', endDate: '2024-08-01' }, - }) - const { - data: applyClicks, - loading: applyClicksLoading, - error: applyClicksError, - } = useQuery(GET_DASHBOARD_APPLY_CLICKS, { - variables: { startDate: '2024-01-01', endDate: '2024-08-01' }, - }) - const { - data: employerClicks, - loading: employerClicksLoading, - error: employerClicksError, - } = useQuery(GET_DASHBOARD_EMPLOYER_CLICKS, { - variables: { startDate: '2024-01-01', endDate: '2024-08-01' }, - }) + const { data, loading, error } = useQuery(GET_ANALYTICS_DASHBOARD_DATA, { + variables: { startDate: "2024-01-01", endDate: "2024-08-01" }, + }); - if (jobClicksLoading || applyClicksLoading || employerClicksLoading) return
loading
+ if (loading) return
loading
; + if (error) return
error
; - const dailyClickCounts = [['Date', 'Daily Count'], ...jobClicks.daily.map((item: any) => [item.date, item.count])] + + const dailyClickCounts = [ + ["Date", "Daily Count"], + ...data.jobClicksDaily.map((item: any) => [item.date, item.count]), + ...data.applyClicksDaily.map((item: any) => [item.date, item.count]), + ...data.employerClicksDaily.map((item: any) => [item.date, item.count]) + ]; const dailyChartOptions = { - title: 'Daily Clicks', - } + title: "Daily Clicks", + }; return ( <>
- +
- ) + ); } -export default Dashboard +export default Dashboard; From 3ecbab364fdc946eb896fb7a1134968b5013d9f8 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Tue, 27 Aug 2024 14:38:13 -0400 Subject: [PATCH 03/37] Style dashboard charts --- frontend/pages/Dashboard.tsx | 112 +++++++++++++++++++-------- frontend/styles/Dashboard.module.css | 37 +++++++++ 2 files changed, 118 insertions(+), 31 deletions(-) diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index d3855bf..6c12dc1 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -1,44 +1,94 @@ -import { useQuery } from "@apollo/client"; -import { - GET_ANALYTICS_DASHBOARD_DATA -} from "../graphql/queries/analyticsQueries"; -import { Chart } from "react-google-charts"; -import styles from "../styles/Dashboard.module.css"; -import Navbar from "../src/components/general/Navbar"; +import { useQuery } from '@apollo/client' +import { GET_ANALYTICS_DASHBOARD_DATA } from '../graphql/queries/analyticsQueries' +import { Chart } from 'react-google-charts' +import styles from '../styles/Dashboard.module.css' +import Navbar from '../src/components/general/Navbar' function Dashboard() { const { data, loading, error } = useQuery(GET_ANALYTICS_DASHBOARD_DATA, { - variables: { startDate: "2024-01-01", endDate: "2024-08-01" }, - }); + variables: { startDate: '2024-01-01', endDate: '2024-08-01' }, + }) - if (loading) return
loading
; - if (error) return
error
; + if (loading) return
loading
+ if (error) return
error
- - const dailyClickCounts = [ - ["Date", "Daily Count"], - ...data.jobClicksDaily.map((item: any) => [item.date, item.count]), - ...data.applyClicksDaily.map((item: any) => [item.date, item.count]), - ...data.employerClicksDaily.map((item: any) => [item.date, item.count]) - ]; - const dailyChartOptions = { - title: "Daily Clicks", - }; + const types = ['Job', 'Apply', 'Employer'] + const intervals = ['Daily', 'Weekly', 'Monthly', 'Yearly'] + let clickChartsIntervals = [] + for (const type of types) { + for (const interval of intervals) { + const dataKey = `${type.toLowerCase()}Clicks${interval}` + const clickCounts = [ + ['Date', `${interval} ${type} Count`], + ...data[dataKey].map((item: any) => [item.date, item.count]), + ] + const chartOptions = { + title: `${interval} ${type} Clicks`, + titlePosition: 'none', + legend: 'none', + backgroundColor: 'transparent', + chartArea: { + width: '100%', + height: '100%', + bottom: '30', + }, + vAxis: { + gridlines: { + color: '#333', + }, + minorGridlines: { + count: 0, + }, + textPosition: 'in', + textStyle: { + color: '#999', + }, + slantedText: false, + maxAlternation: 1, + }, + hAxis: { + textPosition: 'out', + textStyle: { + color: '#999', + }, + slantedText: false, + maxAlternation: 1, + }, + crosshair: { + trigger: 'focus', + color: 'white', + orientation: 'vertical', + }, + tooltip: { + isHtml: true, + }, + axisTitlesPosition: 'none', + } + clickChartsIntervals.push({ clickCounts, chartOptions }) + } + } return ( <> -
- +
+
+ {clickChartsIntervals.map((item: any) => ( +
+ +
+ ))} +
- ); + ) } -export default Dashboard; +export default Dashboard diff --git a/frontend/styles/Dashboard.module.css b/frontend/styles/Dashboard.module.css index e69de29..e1cdf84 100644 --- a/frontend/styles/Dashboard.module.css +++ b/frontend/styles/Dashboard.module.css @@ -0,0 +1,37 @@ +.wrapper { + padding-top: 2rem; + padding-bottom: 2rem; +} + +.charts { + display: flex; + flex-flow: column; + align-items: center; + max-width: 1554px; + margin: 0 auto; + padding-left: 4rem; + padding-right: 4rem; +} + +.chartContainer { + width: 100%; + display: flex; + justify-content: center; + margin-bottom: 4rem; + padding: 2rem 2rem 1rem; + border: #333 1px solid; + border-radius: 1rem; + height: 500px; +} + +.chartContainer :global(div.google-visualization-tooltip) { + background-color: #222; + border: #555 2px solid; + border-radius: 0.5rem; + box-shadow: none; + -webkit-box-shadow: none; +} + +.chartContainer :global(div.google-visualization-tooltip > ul > li > span) { + color: white !important; +} From c4c32275e630ac2717a20787860072fa29e6b51e Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Tue, 27 Aug 2024 14:58:23 -0400 Subject: [PATCH 04/37] Add title to each chart --- frontend/pages/Dashboard.tsx | 24 ++++++++++++++---------- frontend/styles/Dashboard.module.css | 27 +++++++++++++++++---------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index 6c12dc1..8fc026e 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -19,7 +19,7 @@ function Dashboard() { for (const interval of intervals) { const dataKey = `${type.toLowerCase()}Clicks${interval}` const clickCounts = [ - ['Date', `${interval} ${type} Count`], + ['Date', `${interval} ${type} Click Count`], ...data[dataKey].map((item: any) => [item.date, item.count]), ] const chartOptions = { @@ -45,6 +45,7 @@ function Dashboard() { }, slantedText: false, maxAlternation: 1, + format: 'short', }, hAxis: { textPosition: 'out', @@ -53,6 +54,7 @@ function Dashboard() { }, slantedText: false, maxAlternation: 1, + format: 'short', }, crosshair: { trigger: 'focus', @@ -74,15 +76,17 @@ function Dashboard() {
{clickChartsIntervals.map((item: any) => ( -
- +
+

{item.chartOptions.title}

+
+ +
))}
diff --git a/frontend/styles/Dashboard.module.css b/frontend/styles/Dashboard.module.css index e1cdf84..f60966d 100644 --- a/frontend/styles/Dashboard.module.css +++ b/frontend/styles/Dashboard.module.css @@ -4,27 +4,34 @@ } .charts { - display: flex; - flex-flow: column; - align-items: center; max-width: 1554px; margin: 0 auto; padding-left: 4rem; padding-right: 4rem; } -.chartContainer { - width: 100%; +.chart { display: flex; - justify-content: center; + flex-direction: column; + align-items: center; + width: 100%; margin-bottom: 4rem; - padding: 2rem 2rem 1rem; + padding: 1rem 2rem; border: #333 1px solid; border-radius: 1rem; - height: 500px; } -.chartContainer :global(div.google-visualization-tooltip) { +.chartTitle { + font-size: 1.8rem; + margin-bottom: 2rem; +} + +.googleChartContainer { + width: 100%; + height: 450px; +} + +.googleChartContainer :global(div.google-visualization-tooltip) { background-color: #222; border: #555 2px solid; border-radius: 0.5rem; @@ -32,6 +39,6 @@ -webkit-box-shadow: none; } -.chartContainer :global(div.google-visualization-tooltip > ul > li > span) { +.googleChartContainer :global(div.google-visualization-tooltip > ul > li > span) { color: white !important; } From 9ab5bcc25ed234bd2585eded7498a192b9b987f3 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Tue, 27 Aug 2024 15:52:24 -0400 Subject: [PATCH 05/37] Extract click chart in to component --- frontend/pages/Dashboard.tsx | 84 ++++--------------- .../components/admin/DashboardClickChart.tsx | 72 ++++++++++++++++ frontend/styles/Dashboard.module.css | 33 -------- .../components/DashboardClickChart.module.css | 37 ++++++++ 4 files changed, 125 insertions(+), 101 deletions(-) create mode 100644 frontend/src/components/admin/DashboardClickChart.tsx create mode 100644 frontend/styles/components/DashboardClickChart.module.css diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index 8fc026e..98e0a64 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -1,8 +1,8 @@ import { useQuery } from '@apollo/client' import { GET_ANALYTICS_DASHBOARD_DATA } from '../graphql/queries/analyticsQueries' -import { Chart } from 'react-google-charts' import styles from '../styles/Dashboard.module.css' import Navbar from '../src/components/general/Navbar' +import DashboardClickChart from '../src/components/admin/DashboardClickChart' function Dashboard() { const { data, loading, error } = useQuery(GET_ANALYTICS_DASHBOARD_DATA, { @@ -14,80 +14,28 @@ function Dashboard() { const types = ['Job', 'Apply', 'Employer'] const intervals = ['Daily', 'Weekly', 'Monthly', 'Yearly'] - let clickChartsIntervals = [] - for (const type of types) { - for (const interval of intervals) { - const dataKey = `${type.toLowerCase()}Clicks${interval}` - const clickCounts = [ - ['Date', `${interval} ${type} Click Count`], - ...data[dataKey].map((item: any) => [item.date, item.count]), - ] - const chartOptions = { - title: `${interval} ${type} Clicks`, - titlePosition: 'none', - legend: 'none', - backgroundColor: 'transparent', - chartArea: { - width: '100%', - height: '100%', - bottom: '30', - }, - vAxis: { - gridlines: { - color: '#333', - }, - minorGridlines: { - count: 0, - }, - textPosition: 'in', - textStyle: { - color: '#999', - }, - slantedText: false, - maxAlternation: 1, - format: 'short', - }, - hAxis: { - textPosition: 'out', - textStyle: { - color: '#999', - }, - slantedText: false, - maxAlternation: 1, - format: 'short', - }, - crosshair: { - trigger: 'focus', - color: 'white', - orientation: 'vertical', - }, - tooltip: { - isHtml: true, - }, - axisTitlesPosition: 'none', + const typeIntervalClickCharts = types.flatMap((type) => + intervals.map((interval) => { + return { + type, + interval, + dataKey: `${type.toLowerCase()}Clicks${interval}`, } - clickChartsIntervals.push({ clickCounts, chartOptions }) - } - } + }) + ) return ( <>
- {clickChartsIntervals.map((item: any) => ( -
-

{item.chartOptions.title}

-
- -
-
+ {typeIntervalClickCharts.map((chart) => ( + ))}
diff --git a/frontend/src/components/admin/DashboardClickChart.tsx b/frontend/src/components/admin/DashboardClickChart.tsx new file mode 100644 index 0000000..b677b2b --- /dev/null +++ b/frontend/src/components/admin/DashboardClickChart.tsx @@ -0,0 +1,72 @@ +import React from 'react' +import styles from '../../../styles/components/DashboardClickChart.module.css' +import Chart from 'react-google-charts' + +type ClickChartProps = { + data: [ + { + date: String + count: String + } + ] + interval: String + type: String +} + +export default function DashboardClickChart({ data, interval, type }: ClickChartProps) { + const title = `${interval} ${type} Clicks` + const clickCounts = [['Date', `${interval} ${type} Click Count`], ...data.map((item) => [item.date, item.count])] + const chartOptions = { + title, + titlePosition: 'none', + legend: 'none', + backgroundColor: 'transparent', + chartArea: { + width: '100%', + height: '100%', + bottom: '30', + }, + vAxis: { + gridlines: { + color: '#333', + }, + minorGridlines: { + count: 0, + }, + textPosition: 'in', + textStyle: { + color: '#999', + }, + slantedText: false, + maxAlternation: 1, + format: 'short', + }, + hAxis: { + textPosition: 'out', + textStyle: { + color: '#999', + }, + slantedText: false, + maxAlternation: 1, + format: 'short', + }, + crosshair: { + trigger: 'focus', + color: 'white', + orientation: 'vertical', + }, + tooltip: { + isHtml: true, + }, + axisTitlesPosition: 'none', + } + + return ( +
+

{title}

+
+ +
+
+ ) +} diff --git a/frontend/styles/Dashboard.module.css b/frontend/styles/Dashboard.module.css index f60966d..6f97eb2 100644 --- a/frontend/styles/Dashboard.module.css +++ b/frontend/styles/Dashboard.module.css @@ -9,36 +9,3 @@ padding-left: 4rem; padding-right: 4rem; } - -.chart { - display: flex; - flex-direction: column; - align-items: center; - width: 100%; - margin-bottom: 4rem; - padding: 1rem 2rem; - border: #333 1px solid; - border-radius: 1rem; -} - -.chartTitle { - font-size: 1.8rem; - margin-bottom: 2rem; -} - -.googleChartContainer { - width: 100%; - height: 450px; -} - -.googleChartContainer :global(div.google-visualization-tooltip) { - background-color: #222; - border: #555 2px solid; - border-radius: 0.5rem; - box-shadow: none; - -webkit-box-shadow: none; -} - -.googleChartContainer :global(div.google-visualization-tooltip > ul > li > span) { - color: white !important; -} diff --git a/frontend/styles/components/DashboardClickChart.module.css b/frontend/styles/components/DashboardClickChart.module.css new file mode 100644 index 0000000..aa4a1a8 --- /dev/null +++ b/frontend/styles/components/DashboardClickChart.module.css @@ -0,0 +1,37 @@ +.chart { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + margin-bottom: 4rem; + padding: 1rem 2rem; + border: #333 1px solid; + border-radius: 1rem; +} + +.chart:last-child { + margin-bottom: 0; +} + +.chartTitle { + font-size: 1.8rem; + margin-bottom: 2rem; + font-weight: bold; +} + +.googleChartContainer { + width: 100%; + height: 450px; +} + +.googleChartContainer :global(div.google-visualization-tooltip) { + background-color: #222; + border: #555 2px solid; + border-radius: 0.5rem; + box-shadow: none; + -webkit-box-shadow: none; +} + +.googleChartContainer :global(div.google-visualization-tooltip > ul > li > span) { + color: white !important; +} From 11dce23e9446775db60554657f04eb4110e3e65c Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Tue, 27 Aug 2024 17:22:23 -0400 Subject: [PATCH 06/37] Added functionality to change date limits on dashboard --- frontend/pages/Dashboard.tsx | 32 ++++++++++++++++++++++------ frontend/styles/Dashboard.module.css | 3 +++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index 98e0a64..75d97ce 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -1,16 +1,22 @@ -import { useQuery } from '@apollo/client' +import { useLazyQuery, useQuery } from '@apollo/client' import { GET_ANALYTICS_DASHBOARD_DATA } from '../graphql/queries/analyticsQueries' import styles from '../styles/Dashboard.module.css' import Navbar from '../src/components/general/Navbar' import DashboardClickChart from '../src/components/admin/DashboardClickChart' +import { useEffect, useState } from 'react' function Dashboard() { - const { data, loading, error } = useQuery(GET_ANALYTICS_DASHBOARD_DATA, { - variables: { startDate: '2024-01-01', endDate: '2024-08-01' }, - }) + const currentYear = new Date().getFullYear() + const [startDate, setStartDate] = useState(`${currentYear}-01-01`) + const [endDate, setEndDate] = useState(`${currentYear}-12-31`) + const [getPageData, { data: pageData, loading: pageLoading, error: pageError, called: hasPageFetchedOnce }] = + useLazyQuery(GET_ANALYTICS_DASHBOARD_DATA) - if (loading) return
loading
- if (error) return
error
+ useEffect(() => { + if (!hasPageFetchedOnce) { + getPageData({ variables: { startDate, endDate } }) + } + }, [endDate, getPageData, hasPageFetchedOnce, startDate]) const types = ['Job', 'Apply', 'Employer'] const intervals = ['Daily', 'Weekly', 'Monthly', 'Yearly'] @@ -24,15 +30,27 @@ function Dashboard() { }) ) + if (pageLoading || !hasPageFetchedOnce) return
loading
+ if (pageError) { + console.log(pageError.message) + return
error
+ } return ( <>
+
+ setStartDate(e.target.value)} /> + setEndDate(e.target.value)} /> + +
{typeIntervalClickCharts.map((chart) => ( diff --git a/frontend/styles/Dashboard.module.css b/frontend/styles/Dashboard.module.css index 6f97eb2..0d4e4da 100644 --- a/frontend/styles/Dashboard.module.css +++ b/frontend/styles/Dashboard.module.css @@ -3,6 +3,9 @@ padding-bottom: 2rem; } +.dateLimitsForm { +} + .charts { max-width: 1554px; margin: 0 auto; From d5d876d241ba07fd2755a7b70db86ea09cddb158 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Tue, 27 Aug 2024 17:58:32 -0400 Subject: [PATCH 07/37] Styled date limit inputs on dashboard --- frontend/pages/Dashboard.tsx | 28 ++++++++++-- frontend/styles/Dashboard.module.css | 65 ++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 4 deletions(-) diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index 75d97ce..c52d59b 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -40,10 +40,30 @@ function Dashboard() {
- setStartDate(e.target.value)} /> - setEndDate(e.target.value)} /> -
diff --git a/frontend/styles/Dashboard.module.css b/frontend/styles/Dashboard.module.css index 0d4e4da..7a368e9 100644 --- a/frontend/styles/Dashboard.module.css +++ b/frontend/styles/Dashboard.module.css @@ -4,6 +4,71 @@ } .dateLimitsForm { + display: flex; + justify-content: right; + align-items: end; + padding-left: 4rem; + padding-right: 4rem; + margin-bottom: 1rem; +} + +.dateLimitInputContainer { + color: #ddd; + display: flex; + flex-direction: column; + align-items: center; + margin-right: 1rem; +} + +.dateLimitInputLabel, +.dateLimitInput, +.dateLimitSubmit { + color: inherit; + font-family: inherit; + font-size: 0.85rem; +} + +.dateLimitInput, +.dateLimitSubmit { + padding: 0 1rem; + height: 2.2rem; + transition-duration: 150ms; + transition-property: border-color; +} + +.dateLimitInputLabel { + margin-bottom: 0.25rem; +} + +.dateLimitInput { + background-color: transparent; + border: 1px solid #333; + border-radius: 0.5rem; + cursor: text; +} + +.dateLimitInput:hover { + border-color: #666; +} + +.dateLimitInput::-webkit-calendar-picker-indicator { + cursor: pointer; +} + +.dateLimitSubmit { + height: 2.2rem; + padding: 0 2rem; + cursor: pointer; + background-color: #333; + border-width: 2px; + border-style: solid; + border-color: #555; + border-radius: 0.25rem; + font-weight: bold; +} + +.dateLimitSubmit:hover { + border-color: #888; } .charts { From c3be7b18d2593665801993b8ee184f41a3721a72 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Tue, 27 Aug 2024 18:23:05 -0400 Subject: [PATCH 08/37] Check click charts for no data and set date ranges for date limit inputs on dashboard --- frontend/pages/Dashboard.tsx | 13 +++++++++---- .../src/components/admin/DashboardClickChart.tsx | 10 +++++++--- .../components/DashboardClickChart.module.css | 4 ++++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index c52d59b..82c3330 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -6,12 +6,14 @@ import DashboardClickChart from '../src/components/admin/DashboardClickChart' import { useEffect, useState } from 'react' function Dashboard() { - const currentYear = new Date().getFullYear() + const currentDateObj = new Date() + const currentDate = currentDateObj.toISOString().split('T')[0] + const currentYear = currentDateObj.getFullYear() + const [startDate, setStartDate] = useState(`${currentYear}-01-01`) const [endDate, setEndDate] = useState(`${currentYear}-12-31`) const [getPageData, { data: pageData, loading: pageLoading, error: pageError, called: hasPageFetchedOnce }] = useLazyQuery(GET_ANALYTICS_DASHBOARD_DATA) - useEffect(() => { if (!hasPageFetchedOnce) { getPageData({ variables: { startDate, endDate } }) @@ -32,14 +34,14 @@ function Dashboard() { if (pageLoading || !hasPageFetchedOnce) return
loading
if (pageError) { - console.log(pageError.message) + console.error(pageError.message) return
error
} return ( <>
-
+
) } diff --git a/frontend/styles/components/DashboardClickChart.module.css b/frontend/styles/components/DashboardClickChart.module.css index aa4a1a8..001db28 100644 --- a/frontend/styles/components/DashboardClickChart.module.css +++ b/frontend/styles/components/DashboardClickChart.module.css @@ -35,3 +35,7 @@ .googleChartContainer :global(div.google-visualization-tooltip > ul > li > span) { color: white !important; } + +.noDataMessage { + padding-bottom: 1rem; +} From 8bba71f7940f5085d3eb19bbbc7e1961b9443b74 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Wed, 28 Aug 2024 14:18:11 -0400 Subject: [PATCH 09/37] Added table with click type totals to dashboard --- frontend/pages/Dashboard.tsx | 39 ++++++++++----- .../components/admin/DashboardClickChart.tsx | 8 ++-- .../admin/DashboardTwoItemTable.tsx | 30 ++++++++++++ frontend/styles/Dashboard.module.css | 22 +++++++-- .../components/DashboardClickChart.module.css | 5 -- .../DashboardTwoItemTable.module.css | 47 +++++++++++++++++++ 6 files changed, 124 insertions(+), 27 deletions(-) create mode 100644 frontend/src/components/admin/DashboardTwoItemTable.tsx create mode 100644 frontend/styles/components/DashboardTwoItemTable.module.css diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index 82c3330..2539202 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -1,9 +1,15 @@ -import { useLazyQuery, useQuery } from '@apollo/client' +import { useLazyQuery } from '@apollo/client' +import { useEffect, useState } from 'react' import { GET_ANALYTICS_DASHBOARD_DATA } from '../graphql/queries/analyticsQueries' -import styles from '../styles/Dashboard.module.css' -import Navbar from '../src/components/general/Navbar' import DashboardClickChart from '../src/components/admin/DashboardClickChart' -import { useEffect, useState } from 'react' +import Navbar from '../src/components/general/Navbar' +import styles from '../styles/Dashboard.module.css' +import DashboardTwoItemTable from '../src/components/admin/DashboardTwoItemTable' + +type DashboardPageDatum = { + date: string + count: number +} function Dashboard() { const currentDateObj = new Date() @@ -22,7 +28,7 @@ function Dashboard() { const types = ['Job', 'Apply', 'Employer'] const intervals = ['Daily', 'Weekly', 'Monthly', 'Yearly'] - const typeIntervalClickCharts = types.flatMap((type) => + const typeIntervalClickGroups = types.flatMap((type) => intervals.map((interval) => { return { type, @@ -37,6 +43,13 @@ function Dashboard() { console.error(pageError.message) return
error
} + + const typeClickTableBodyData = types.map((type) => { + const dataKey = `${type.toLowerCase()}Clicks${intervals[0]}` // can be any interval since they all have same amount of total clicks + const totalClicks = pageData[dataKey].reduce((prev: number, cur: DashboardPageDatum) => prev + cur.count, 0) + return [type, totalClicks] + }) + return ( <> @@ -71,14 +84,14 @@ function Dashboard() { Get Analytics -
- {typeIntervalClickCharts.map((chart) => ( - +
+ +
+
+ {typeIntervalClickGroups.map((chart) => ( +
+ +
))}
diff --git a/frontend/src/components/admin/DashboardClickChart.tsx b/frontend/src/components/admin/DashboardClickChart.tsx index 277327c..e097ab8 100644 --- a/frontend/src/components/admin/DashboardClickChart.tsx +++ b/frontend/src/components/admin/DashboardClickChart.tsx @@ -5,12 +5,12 @@ import Chart from 'react-google-charts' type ClickChartProps = { data: [ { - date: String - count: String + date: string + count: number } ] - interval: String - type: String + interval: string + type: string } export default function DashboardClickChart({ data, interval, type }: ClickChartProps) { diff --git a/frontend/src/components/admin/DashboardTwoItemTable.tsx b/frontend/src/components/admin/DashboardTwoItemTable.tsx new file mode 100644 index 0000000..c096ffc --- /dev/null +++ b/frontend/src/components/admin/DashboardTwoItemTable.tsx @@ -0,0 +1,30 @@ +import styles from '../../../styles/components/DashboardTwoItemTable.module.css' + +type DashboardTwoItemTableProps = { + firstHeading: string + secondHeading: string + data: Array> +} + +export default function DashboardTwoItemTable({ firstHeading, secondHeading, data }: DashboardTwoItemTableProps) { + return ( +
+ + + + + + + + + {data.map((row) => ( + + + + + ))} + +
{firstHeading}{secondHeading}
{row[0]}{row[1]}
+
+ ) +} diff --git a/frontend/styles/Dashboard.module.css b/frontend/styles/Dashboard.module.css index 7a368e9..7d16c13 100644 --- a/frontend/styles/Dashboard.module.css +++ b/frontend/styles/Dashboard.module.css @@ -1,6 +1,10 @@ .wrapper { + padding-left: 4rem; + padding-right: 4rem; padding-top: 2rem; padding-bottom: 2rem; + max-width: 1554px; + margin: 0 auto; } .dateLimitsForm { @@ -71,9 +75,17 @@ border-color: #888; } -.charts { - max-width: 1554px; - margin: 0 auto; - padding-left: 4rem; - padding-right: 4rem; +.quickStats { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1rem; + margin-bottom: 3rem; +} + +.chartContainer { + margin-bottom: 3rem; +} + +.chartContainer:last-child { + margin-bottom: 0; } diff --git a/frontend/styles/components/DashboardClickChart.module.css b/frontend/styles/components/DashboardClickChart.module.css index 001db28..13c2395 100644 --- a/frontend/styles/components/DashboardClickChart.module.css +++ b/frontend/styles/components/DashboardClickChart.module.css @@ -3,16 +3,11 @@ flex-direction: column; align-items: center; width: 100%; - margin-bottom: 4rem; padding: 1rem 2rem; border: #333 1px solid; border-radius: 1rem; } -.chart:last-child { - margin-bottom: 0; -} - .chartTitle { font-size: 1.8rem; margin-bottom: 2rem; diff --git a/frontend/styles/components/DashboardTwoItemTable.module.css b/frontend/styles/components/DashboardTwoItemTable.module.css new file mode 100644 index 0000000..a42decc --- /dev/null +++ b/frontend/styles/components/DashboardTwoItemTable.module.css @@ -0,0 +1,47 @@ +.dataTable table, +.dataTable thead, +.dataTable tbody { + display: block; + width: 100%; +} + +.dataTable tr { + display: flex; + justify-content: space-between; + align-items: center; +} + +.dataTable thead tr { + border: #333 1px solid; + border-radius: 1rem 1rem 0 0; + padding: 1.5rem 1rem; +} + +.dataTable th:last-child { + font-weight: normal; + color: #888; + font-size: 0.9em; +} + +.dataTable tbody { + border: #333 1px solid; + border-radius: 0 0 1rem 1rem; + padding: 1rem; +} + +.dataTable tbody tr { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.dataTable tbody tr:last-child { + padding-bottom: 0; +} + +.dataTable tbody tr:first-child { + padding-top: 0; +} + +.dataTable td:last-child { + font-weight: bold; +} From e36651b32dfb680dcbacb1306376f1b082dadd38 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Wed, 28 Aug 2024 14:40:30 -0400 Subject: [PATCH 10/37] Added title displaying current date range in dashboard --- frontend/pages/Dashboard.tsx | 65 ++++++++++--------- .../components/admin/DashboardClickChart.tsx | 2 +- frontend/styles/Dashboard.module.css | 27 +++++++- .../components/DashboardClickChart.module.css | 4 +- .../DashboardTwoItemTable.module.css | 5 ++ 5 files changed, 67 insertions(+), 36 deletions(-) diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index 2539202..dc73b1d 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -54,36 +54,41 @@ function Dashboard() { <>
-
- - - -
+
+

+ Analytics for {startDate} to {endDate} +

+
+ + + +
+
diff --git a/frontend/src/components/admin/DashboardClickChart.tsx b/frontend/src/components/admin/DashboardClickChart.tsx index e097ab8..48254b4 100644 --- a/frontend/src/components/admin/DashboardClickChart.tsx +++ b/frontend/src/components/admin/DashboardClickChart.tsx @@ -63,7 +63,7 @@ export default function DashboardClickChart({ data, interval, type }: ClickChart return (
-

{title}

+
{title}
{data.length ? (
diff --git a/frontend/styles/Dashboard.module.css b/frontend/styles/Dashboard.module.css index 7d16c13..a438b18 100644 --- a/frontend/styles/Dashboard.module.css +++ b/frontend/styles/Dashboard.module.css @@ -7,13 +7,26 @@ margin: 0 auto; } +.titleRow { + display: flex; + flex-direction: column; + justify-content: start; + align-items: center; + gap: 2rem; + margin-bottom: 2rem; +} + +.mainTitle { + margin: 0; + font-size: 1.8rem; + font-weight: bold; + text-align: left; +} + .dateLimitsForm { display: flex; justify-content: right; align-items: end; - padding-left: 4rem; - padding-right: 4rem; - margin-bottom: 1rem; } .dateLimitInputContainer { @@ -89,3 +102,11 @@ .chartContainer:last-child { margin-bottom: 0; } + +@media screen and (min-width: 1064px) { + .titleRow { + flex-direction: row; + justify-content: space-between; + align-items: center; + } +} diff --git a/frontend/styles/components/DashboardClickChart.module.css b/frontend/styles/components/DashboardClickChart.module.css index 13c2395..0ec0669 100644 --- a/frontend/styles/components/DashboardClickChart.module.css +++ b/frontend/styles/components/DashboardClickChart.module.css @@ -3,13 +3,13 @@ flex-direction: column; align-items: center; width: 100%; - padding: 1rem 2rem; + padding: 2rem 2rem 1rem; border: #333 1px solid; border-radius: 1rem; } .chartTitle { - font-size: 1.8rem; + font-size: 1.6rem; margin-bottom: 2rem; font-weight: bold; } diff --git a/frontend/styles/components/DashboardTwoItemTable.module.css b/frontend/styles/components/DashboardTwoItemTable.module.css index a42decc..f80806c 100644 --- a/frontend/styles/components/DashboardTwoItemTable.module.css +++ b/frontend/styles/components/DashboardTwoItemTable.module.css @@ -1,3 +1,7 @@ +.dataTable { + overflow-x: auto; +} + .dataTable table, .dataTable thead, .dataTable tbody { @@ -9,6 +13,7 @@ display: flex; justify-content: space-between; align-items: center; + column-gap: 1rem; } .dataTable thead tr { From 5e3a2f9c5e2ce2456004b5324820e1c37ae000c7 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Wed, 28 Aug 2024 14:56:08 -0400 Subject: [PATCH 11/37] Fixed wrong date range showing in title when changing the dates in dashboard --- frontend/pages/Dashboard.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index dc73b1d..8a630d4 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -18,8 +18,15 @@ function Dashboard() { const [startDate, setStartDate] = useState(`${currentYear}-01-01`) const [endDate, setEndDate] = useState(`${currentYear}-12-31`) + const [fetchedStartDate, setFetchedStartDate] = useState(startDate) + const [fetchedEndDate, setFetchedEndDate] = useState(endDate) const [getPageData, { data: pageData, loading: pageLoading, error: pageError, called: hasPageFetchedOnce }] = - useLazyQuery(GET_ANALYTICS_DASHBOARD_DATA) + useLazyQuery(GET_ANALYTICS_DASHBOARD_DATA, { + onCompleted: () => { + setFetchedStartDate(startDate) + setFetchedEndDate(endDate) + }, + }) useEffect(() => { if (!hasPageFetchedOnce) { getPageData({ variables: { startDate, endDate } }) @@ -56,7 +63,7 @@ function Dashboard() {

- Analytics for {startDate} to {endDate} + Analytics for {fetchedStartDate} to {fetchedEndDate}

- +
{typeIntervalClickGroups.map((chart) => ( diff --git a/frontend/styles/components/DashboardTwoItemTable.module.css b/frontend/styles/components/DashboardTwoItemTable.module.css index f80806c..55ee334 100644 --- a/frontend/styles/components/DashboardTwoItemTable.module.css +++ b/frontend/styles/components/DashboardTwoItemTable.module.css @@ -22,12 +22,6 @@ padding: 1.5rem 1rem; } -.dataTable th:last-child { - font-weight: normal; - color: #888; - font-size: 0.9em; -} - .dataTable tbody { border: #333 1px solid; border-radius: 0 0 1rem 1rem; @@ -46,7 +40,3 @@ .dataTable tbody tr:first-child { padding-top: 0; } - -.dataTable td:last-child { - font-weight: bold; -} From a8eace8114d6e73da2767b2e0f3c67890faadf1f Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Wed, 28 Aug 2024 22:24:45 -0400 Subject: [PATCH 14/37] Added table of job tag rankings by clicks in dashboard --- frontend/graphql/queries/analyticsQueries.ts | 401 ++++++++++-------- frontend/pages/Dashboard.tsx | 27 +- .../DashboardTwoItemTable.module.css | 12 +- 3 files changed, 261 insertions(+), 179 deletions(-) diff --git a/frontend/graphql/queries/analyticsQueries.ts b/frontend/graphql/queries/analyticsQueries.ts index e6c5a5c..74ac1b0 100644 --- a/frontend/graphql/queries/analyticsQueries.ts +++ b/frontend/graphql/queries/analyticsQueries.ts @@ -1,207 +1,207 @@ -import { gql } from '@apollo/client'; +import { gql } from '@apollo/client' // Query for getting job clicks export const GET_JOB_CLICKS = gql` - query GetJobClicks { - getJobClicks { - jobTitle - employerId - employerName - jobId - clickTime - scholarName - scholarEmail - } - } -`; + query GetJobClicks { + getJobClicks { + jobTitle + employerId + employerName + jobId + clickTime + scholarName + scholarEmail + } + } +` // Query for getting apply clicks export const GET_APPLY_CLICKS = gql` - query GetApplyClicks { - getApplyClicks { - scholarId - jobId - clickTime - scholarName - scholarEmail - jobTitle - employerName - } - } -`; + query GetApplyClicks { + getApplyClicks { + scholarId + jobId + clickTime + scholarName + scholarEmail + jobTitle + employerName + } + } +` // Query for getting employer clicks export const GET_EMPLOYER_CLICKS = gql` - query GetEmployerClicks { - getEmployerClicks { - scholarId - employerId - employerName - clickTime - scholarName - scholarEmail - } - } -`; + query GetEmployerClicks { + getEmployerClicks { + scholarId + employerId + employerName + clickTime + scholarName + scholarEmail + } + } +` // Query for getting ranked job clicks export const GET_JOB_CLICKS_RANKED = gql` - query GetJobClicksRanked { - getJobClicksRanked { - employerId - jobId - jobTitle - employerName - click_count - } - } -`; + query GetJobClicksRanked { + getJobClicksRanked { + employerId + jobId + jobTitle + employerName + click_count + } + } +` // Query for getting job tag rankings export const GET_JOB_TAG_RANKING = gql` - query GetJobTagRanking { - getJobTagRanking { - tag - job_count - } + query GetJobTagRanking { + getJobTagRanking { + tag + job_count } -`; + } +` // Query for getting job tag ranking by clicks export const GET_JOB_TAG_RANKING_BY_CLICKS = gql` - query GetJobTagRankingByClicks { - getJobTagRankingByClicks { - tag - click_count - } + query GetJobTagRankingByClicks { + getJobTagRankingByClicks { + tag + click_count } -`; + } +` // Query for getting job location ranking export const GET_JOB_LOCATION_RANKING = gql` - query GetJobLocationRanking { - getJobLocationRanking { - location - job_count - } + query GetJobLocationRanking { + getJobLocationRanking { + location + job_count } -`; + } +` // Query for getting job deadline ranking by month export const GET_JOB_DEADLINE_RANKING_BY_MONTH = gql` - query GetJobDeadlineRankingByMonth { - getJobDeadlineRankingByMonth { - month - job_count - } + query GetJobDeadlineRankingByMonth { + getJobDeadlineRankingByMonth { + month + job_count } -`; + } +` // Query for getting scholars ranked by major export const GET_SCHOLARS_RANKED_BY_MAJOR = gql` - query GetScholarsRankedByMajor { - getScholarsRankedByMajor { - major - scholar_count - } + query GetScholarsRankedByMajor { + getScholarsRankedByMajor { + major + scholar_count } -`; + } +` // Query for getting scholars ranked by year export const GET_SCHOLARS_RANKED_BY_YEAR = gql` - query GetScholarsRankedByYear { - getScholarsRankedByYear { - year - scholar_count - } + query GetScholarsRankedByYear { + getScholarsRankedByYear { + year + scholar_count } -`; + } +` // Query for getting the percentage of scholars with allowed notifications export const GET_PERCENTAGE_OF_SCHOLARS_WITH_ALLOWED_NOTIFICATIONS = gql` - query GetPercentageOfScholarsWithAllowedNotifications { - getPercentageOfScholarsWithAllowedNotifications - } -`; + query GetPercentageOfScholarsWithAllowedNotifications { + getPercentageOfScholarsWithAllowedNotifications + } +` // Query for getting scholar apply clicks ranked export const GET_SCHOLAR_APPLY_CLICKS_RANKED = gql` - query GetScholarApplyClicksRanked { - getScholarApplyClicksRanked { - scholarId - apply_count - scholarName - scholarEmail - } - } -`; + query GetScholarApplyClicksRanked { + getScholarApplyClicksRanked { + scholarId + apply_count + scholarName + scholarEmail + } + } +` // Query for getting scholar job clicks ranked export const GET_SCHOLAR_JOB_CLICKS_RANKED = gql` - query GetScholarJobClicksRanked { - getScholarJobClicksRanked { - scholarId - job_count - scholarName - scholarEmail - } - } -`; + query GetScholarJobClicksRanked { + getScholarJobClicksRanked { + scholarId + job_count + scholarName + scholarEmail + } + } +` // Query for getting scholar employer clicks ranked export const GET_SCHOLAR_EMPLOYER_CLICKS_RANKED = gql` - query GetScholarEmployerClicksRanked { - getScholarEmployerClicksRanked { - scholarId - employer_count - scholarName - scholarEmail - } - } -`; + query GetScholarEmployerClicksRanked { + getScholarEmployerClicksRanked { + scholarId + employer_count + scholarName + scholarEmail + } + } +` // Query for getting employer job postings ranking export const GET_EMPLOYER_JOB_POSTINGS_RANKING = gql` - query GetEmployerJobPostingsRanking { - getEmployerJobPostingsRanking { - employerName - employerId - job_posting_click_count - } - } -`; + query GetEmployerJobPostingsRanking { + getEmployerJobPostingsRanking { + employerName + employerId + job_posting_click_count + } + } +` // Query for getting the number of days since last job post by employer export const GET_NUM_DAYS_SINCE_LAST_JOB_POST_BY_EMPLOYER = gql` - query GetNumDaysSinceLastJobPostByEmployer { - getNumDaysSinceLastJobPostByEmployer { - employerName - employerId - days_since_last_post - } - } -`; + query GetNumDaysSinceLastJobPostByEmployer { + getNumDaysSinceLastJobPostByEmployer { + employerName + employerId + days_since_last_post + } + } +` // Query for getting the most popular job tags by employer export const GET_MOST_POPULAR_JOB_TAGS_BY_EMPLOYER = gql` - query GetMostPopularJobTagsByEmployer { - getMostPopularJobTagsByEmployer { - employer - tag - job_count - } - } -`; + query GetMostPopularJobTagsByEmployer { + getMostPopularJobTagsByEmployer { + employer + tag + job_count + } + } +` // Query for getting scholar clicks by school export const GET_SCHOLAR_CLICKS_BY_SCHOOL = gql` - query GetScholarClicksBySchool { - getScholarClicksBySchool { - school - scholar_click_count - } + query GetScholarClicksBySchool { + getScholarClicksBySchool { + school + scholar_click_count } -`; + } +` export const GET_ALL_CLICK_COUNTS = gql` query GetAllClickCounts { @@ -233,7 +233,7 @@ export const GET_ALL_CLICK_COUNTS = gql` count } } -`; +` export const GET_JOB_CLICKS_FOR_SCHOLAR = gql` query GetJobClicksForScholar($scholarId: Int!) { @@ -246,8 +246,7 @@ export const GET_JOB_CLICKS_FOR_SCHOLAR = gql` employerName } } -`; - +` export const GET_APPLY_CLICKS_FOR_SCHOLAR = gql` query GetApplyClicksForScholar($scholarId: Int!) { @@ -260,7 +259,7 @@ export const GET_APPLY_CLICKS_FOR_SCHOLAR = gql` employerName } } -`; +` export const GET_EMPLOYER_CLICKS_FOR_SCHOLAR = gql` query GetEmployerClicksForScholar($scholarId: Int!) { @@ -272,78 +271,142 @@ export const GET_EMPLOYER_CLICKS_FOR_SCHOLAR = gql` employerName } } -`; +` export const GET_NUMBER_OF_ACTIVE_SCHOLARS = gql` query GetNumberOfActiveScholars { getNumberOfActiveScholars } -`; +` export const GET_NUMBER_OF_ALLOWED_SCHOLARS = gql` query GetNumberOfAllowedScholars { getNumberOfAllowedScholars } -`; +` export const GET_CLICKS_CUSTOM_ANALYTICS = gql` - query GetClicksCustomAnalytics($startDate: Date!, $endDate: Date!, $interval: String!, $clickType: String!) { - getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: $interval, clickType: $clickType) { - date - count - } + query GetClicksCustomAnalytics($startDate: Date!, $endDate: Date!, $interval: String!, $clickType: String!) { + getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: $interval, clickType: $clickType) { + date + count } - `; + } +` export const GET_ANALYTICS_DASHBOARD_DATA = gql` query GetAnalyticsDashboardData($startDate: Date!, $endDate: Date!) { - jobClicksDaily: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "daily", clickType: "job") { + jobClicksDaily: getClicksCustomAnalytics( + startDate: $startDate + endDate: $endDate + interval: "daily" + clickType: "job" + ) { date count } - jobClicksWeekly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "weekly", clickType: "job") { + jobClicksWeekly: getClicksCustomAnalytics( + startDate: $startDate + endDate: $endDate + interval: "weekly" + clickType: "job" + ) { date count } - jobClicksMonthly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "monthly", clickType: "job") { + jobClicksMonthly: getClicksCustomAnalytics( + startDate: $startDate + endDate: $endDate + interval: "monthly" + clickType: "job" + ) { date count } - jobClicksYearly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "yearly", clickType: "job") { + jobClicksYearly: getClicksCustomAnalytics( + startDate: $startDate + endDate: $endDate + interval: "yearly" + clickType: "job" + ) { date count } - applyClicksDaily: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "daily", clickType: "apply") { + applyClicksDaily: getClicksCustomAnalytics( + startDate: $startDate + endDate: $endDate + interval: "daily" + clickType: "apply" + ) { date count } - applyClicksWeekly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "weekly", clickType: "apply") { + applyClicksWeekly: getClicksCustomAnalytics( + startDate: $startDate + endDate: $endDate + interval: "weekly" + clickType: "apply" + ) { date count } - applyClicksMonthly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "monthly", clickType: "apply") { + applyClicksMonthly: getClicksCustomAnalytics( + startDate: $startDate + endDate: $endDate + interval: "monthly" + clickType: "apply" + ) { date count } - applyClicksYearly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "yearly", clickType: "apply") { + applyClicksYearly: getClicksCustomAnalytics( + startDate: $startDate + endDate: $endDate + interval: "yearly" + clickType: "apply" + ) { date count } - employerClicksDaily: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "daily", clickType: "employer") { + employerClicksDaily: getClicksCustomAnalytics( + startDate: $startDate + endDate: $endDate + interval: "daily" + clickType: "employer" + ) { date count } - employerClicksWeekly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "weekly", clickType: "employer") { + employerClicksWeekly: getClicksCustomAnalytics( + startDate: $startDate + endDate: $endDate + interval: "weekly" + clickType: "employer" + ) { date count } - employerClicksMonthly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "monthly", clickType: "employer") { + employerClicksMonthly: getClicksCustomAnalytics( + startDate: $startDate + endDate: $endDate + interval: "monthly" + clickType: "employer" + ) { date count } - employerClicksYearly: getClicksCustomAnalytics(startDate: $startDate, endDate: $endDate, interval: "yearly", clickType: "employer") { + employerClicksYearly: getClicksCustomAnalytics( + startDate: $startDate + endDate: $endDate + interval: "yearly" + clickType: "employer" + ) { date count } + jobTagRankingsByClicks: getJobTagRankingByClicks { + tag + click_count + } } -`; \ No newline at end of file +` diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index fc711f9..a3fa21b 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -6,7 +6,7 @@ import Navbar from '../src/components/general/Navbar' import styles from '../styles/Dashboard.module.css' import DashboardTwoItemTable from '../src/components/admin/DashboardTwoItemTable' -type DashboardPageDatum = { +type TypeClickDatum = { date: string count: number } @@ -15,11 +15,11 @@ function Dashboard() { const currentDateObj = new Date() const currentDate = currentDateObj.toISOString().split('T')[0] const currentYear = currentDateObj.getFullYear() - const [startDate, setStartDate] = useState(`${currentYear}-01-01`) const [endDate, setEndDate] = useState(`${currentYear}-12-31`) const [fetchedStartDate, setFetchedStartDate] = useState(startDate) const [fetchedEndDate, setFetchedEndDate] = useState(endDate) + const [getPageData, { data: pageData, loading: pageLoading, error: pageError, called: hasPageFetchedOnce }] = useLazyQuery(GET_ANALYTICS_DASHBOARD_DATA, { onCompleted: () => { @@ -33,6 +33,15 @@ function Dashboard() { } }, [endDate, getPageData, hasPageFetchedOnce, startDate]) + if (pageLoading || !hasPageFetchedOnce) return
loading
+ if (pageError) { + console.error(pageError.message) + return
error
+ } + + const jobTagRankingsByClicks = pageData['jobTagRankingsByClicks'] + const jobTagRankingsTableBodyData = jobTagRankingsByClicks.map((ranking) => [ranking.tag, ranking.click_count]) + const types = ['Job', 'Apply', 'Employer'] const intervals = ['Daily', 'Weekly', 'Monthly', 'Yearly'] const typeIntervalClickGroups = types.flatMap((type) => @@ -45,15 +54,9 @@ function Dashboard() { }) ) - if (pageLoading || !hasPageFetchedOnce) return
loading
- if (pageError) { - console.error(pageError.message) - return
error
- } - const typeClickTableBodyData = types.map((type) => { const dataKey = `${type.toLowerCase()}Clicks${intervals[0]}` // can be any interval since they all have same amount of total clicks - const totalClicks = pageData[dataKey].reduce((prev: number, cur: DashboardPageDatum) => prev + cur.count, 0) + const totalClicks = pageData[dataKey].reduce((prev: number, cur: TypeClickDatum) => prev + cur.count, 0) return [type, totalClicks] }) @@ -98,7 +101,13 @@ function Dashboard() {
+
+
{typeIntervalClickGroups.map((chart) => (
diff --git a/frontend/styles/components/DashboardTwoItemTable.module.css b/frontend/styles/components/DashboardTwoItemTable.module.css index 55ee334..b10742b 100644 --- a/frontend/styles/components/DashboardTwoItemTable.module.css +++ b/frontend/styles/components/DashboardTwoItemTable.module.css @@ -1,8 +1,15 @@ .dataTable { overflow-x: auto; + display: flex; + align-items: stretch; +} + +.dataTable table { + display: flex; + flex-direction: column; + width: 100%; } -.dataTable table, .dataTable thead, .dataTable tbody { display: block; @@ -26,6 +33,9 @@ border: #333 1px solid; border-radius: 0 0 1rem 1rem; padding: 1rem; + max-height: 300px; + overflow-y: auto; + flex-grow: 1; } .dataTable tbody tr { From fa0adae3f1112f96f0ad4a861c58aef7066b374f Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Wed, 28 Aug 2024 23:12:25 -0400 Subject: [PATCH 15/37] Added tags by job count and scholars by major sections to dashboard --- frontend/graphql/queries/analyticsQueries.ts | 8 +++++ frontend/pages/Dashboard.tsx | 31 ++++++++++++-------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/frontend/graphql/queries/analyticsQueries.ts b/frontend/graphql/queries/analyticsQueries.ts index 74ac1b0..6c25c30 100644 --- a/frontend/graphql/queries/analyticsQueries.ts +++ b/frontend/graphql/queries/analyticsQueries.ts @@ -408,5 +408,13 @@ export const GET_ANALYTICS_DASHBOARD_DATA = gql` tag click_count } + jobTagRankings: getJobTagRanking { + tag + job_count + } + scholarsRankedByMajor: getScholarsRankedByMajor { + major + scholar_count + } } ` diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index a3fa21b..7dbe283 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -39,8 +39,11 @@ function Dashboard() { return
error
} - const jobTagRankingsByClicks = pageData['jobTagRankingsByClicks'] - const jobTagRankingsTableBodyData = jobTagRankingsByClicks.map((ranking) => [ranking.tag, ranking.click_count]) + const scholarsByMajor: [{ major: string; scholar_count: string }] = pageData['scholarsRankedByMajor'] + const scholarsByMajorTableBodyData = scholarsByMajor.map((ranking) => [ranking.major, ranking.scholar_count]) + + const jobTagsByClicks: [{ tag: string; click_count: string }] = pageData['jobTagRankingsByClicks'] + const jobTagsTableBodyData = jobTagsByClicks.map((ranking) => [ranking.tag, ranking.click_count]) const types = ['Job', 'Apply', 'Employer'] const intervals = ['Daily', 'Weekly', 'Monthly', 'Yearly'] @@ -65,9 +68,16 @@ function Dashboard() {
-

- Analytics for {fetchedStartDate} to {fetchedEndDate} -

+

Scholar and Job Counts

+
+
+ + +
+
+

+ Click Counts from {fetchedStartDate} to {fetchedEndDate} +

- - + +
-
{typeIntervalClickGroups.map((chart) => (
From 8b65163609b257c044becd33a628bf648a22fcad Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Thu, 29 Aug 2024 11:16:32 -0400 Subject: [PATCH 16/37] Format analytics typeDefs --- .../src/graphql/typeDefs/analytics.typedef.ts | 229 +++++++++--------- 1 file changed, 114 insertions(+), 115 deletions(-) diff --git a/backend/src/graphql/typeDefs/analytics.typedef.ts b/backend/src/graphql/typeDefs/analytics.typedef.ts index a0ad49d..82bc6a8 100644 --- a/backend/src/graphql/typeDefs/analytics.typedef.ts +++ b/backend/src/graphql/typeDefs/analytics.typedef.ts @@ -1,8 +1,7 @@ -import {gql} from "apollo-server-lambda"; +import { gql } from 'apollo-server-lambda' export const analyticsTypeDefs = gql` - - type Query { + type Query { getJobClicks: [JobClick] getApplyClicks: [ApplyClicks] getEmployerClicks: [EmployerClick] @@ -38,166 +37,166 @@ export const analyticsTypeDefs = gql` getNumberOfActiveScholars: Int getNumberOfAllowedScholars: Int getClicksCustomAnalytics(startDate: Date, endDate: Date, interval: String, clickType: String): [CustomAnalytics] - } + } - type Mutation { + type Mutation { logJobClick(scholarId: Int!, jobId: Int!): JobClick logEmployerClick(scholarId: Int!, employerId: Int!): EmployerClick logApplyClick(scholarId: Int!, jobId: Int!): ApplyClick - } + } - type CustomAnalytics { - date: Date - count: Int - } + type CustomAnalytics { + date: Date + count: Int + } - type ScholarJobClicks { + type ScholarJobClicks { jobId: Int clickTime: String scholarEmail: String scholarName: String jobTitle: String employerName: String - } + } - type ScholarApplyClicks { + type ScholarApplyClicks { jobId: Int clickTime: String scholarEmail: String scholarName: String jobTitle: String employerName: String - } + } - type ScholarEmployerClicks { + type ScholarEmployerClicks { employerId: Int clickTime: String scholarEmail: String scholarName: String employerName: String - } - - type ClickCount { - count: Int - } - - type JobClicksLastWeek { - scholarId: Int - jobId: Int - clickTime: String - scholarEmail: String - scholarName: String - jobTitle: String - employerName: String - } - - type ScholarJobClicks { - scholarId: Int - jobId: Int - clickTime: String - scholarEmail: String - scholarName: String - jobTitle: String - employerName: String - } - - type ScholarApplyClicks { - scholarId: Int - jobId: Int - clickTime: String - scholarEmail: String - scholarName: String - jobTitle: String - employerName: String - } - - type ScholarEmployerClicks { - scholarId: Int - employerId: Int - clickTime: String - scholarEmail: String - scholarName: String - employerName: String - } - - type ApplyClicks { - scholarId: Int - jobId: Int - clickTime: String - scholarEmail: String - scholarName: String - jobTitle: String - employerName: String - } - - type ApplyClick { + } + + type ClickCount { + count: Int + } + + type JobClicksLastWeek { + scholarId: Int + jobId: Int + clickTime: String + scholarEmail: String + scholarName: String + jobTitle: String + employerName: String + } + + type ScholarJobClicks { scholarId: Int jobId: Int clickTime: String - } + scholarEmail: String + scholarName: String + jobTitle: String + employerName: String + } + + type ScholarApplyClicks { + scholarId: Int + jobId: Int + clickTime: String + scholarEmail: String + scholarName: String + jobTitle: String + employerName: String + } - type EmployerJobTagRanking { + type ScholarEmployerClicks { + scholarId: Int + employerId: Int + clickTime: String + scholarEmail: String + scholarName: String + employerName: String + } + + type ApplyClicks { + scholarId: Int + jobId: Int + clickTime: String + scholarEmail: String + scholarName: String + jobTitle: String + employerName: String + } + + type ApplyClick { + scholarId: Int + jobId: Int + clickTime: String + } + + type EmployerJobTagRanking { employer: String tag: String job_count: Int - } + } - type EmployerJobPostingRank { + type EmployerJobPostingRank { employerName: String employerId: String job_posting_click_count: Int - } + } - type EmployerLastJobPost { + type EmployerLastJobPost { employerName: String employerId: String days_since_last_post: Int - } + } + + scalar Date - scalar Date - - type MajorRanking { + type MajorRanking { major: String scholar_count: Int - } + } - type ScholarClicksBySchool { + type ScholarClicksBySchool { school: String scholar_click_count: Int - } + } - type YearRanking { + type YearRanking { year: Int scholar_count: Int - } + } - type JobApplyClickRank { + type JobApplyClickRank { jobId: Int apply_count: Int - } + } - type ApplyClickRank { + type ApplyClickRank { scholarId: Int apply_count: Int scholarName: String scholarEmail: String - } + } - type JobClickRank { + type JobClickRank { scholarId: Int job_count: Int scholarName: String scholarEmail: String - } + } - type EmployerClickRank { - scholarId: Int + type EmployerClickRank { + scholarId: Int employer_count: Int scholarName: String scholarEmail: String - } + } - type JobClick { + type JobClick { employerId: Int jobId: Int jobTitle: String @@ -205,54 +204,54 @@ export const analyticsTypeDefs = gql` clickTime: String scholarName: String scholarEmail: String - } + } - type RankedJobClick { + type RankedJobClick { employerId: Int jobId: Int jobTitle: String employerName: String click_count: Int - } + } - type RankedEmployerClick { + type RankedEmployerClick { employerId: Int employerName: String click_count: Int - } + } - type ApplyClick { + type ApplyClick { scholarId: Int jobId: Int clickTime: String - } + } - type JobTagRanking { + type JobTagRanking { tag: String job_count: Int - } + } - type JobTagRankingByClick { + type JobTagRankingByClick { tag: String click_count: Int - } + } - type JobLocationRanking { + type JobLocationRanking { location: String job_count: Int - } + } - type JobDeadlineRanking { + type JobDeadlineRanking { month: String job_count: Int - } - - type EmployerClick { + } + + type EmployerClick { scholarId: Int employerId: Int employerName: String clickTime: String scholarName: String scholarEmail: String - } -` \ No newline at end of file + } +` From 0cf5d2371029be150101cbfd37ca0676526b8471 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Thu, 29 Aug 2024 11:29:59 -0400 Subject: [PATCH 17/37] Minor type change in dashboard --- frontend/pages/Dashboard.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index 7dbe283..b8fe706 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -6,11 +6,6 @@ import Navbar from '../src/components/general/Navbar' import styles from '../styles/Dashboard.module.css' import DashboardTwoItemTable from '../src/components/admin/DashboardTwoItemTable' -type TypeClickDatum = { - date: string - count: number -} - function Dashboard() { const currentDateObj = new Date() const currentDate = currentDateObj.toISOString().split('T')[0] @@ -59,7 +54,16 @@ function Dashboard() { const typeClickTableBodyData = types.map((type) => { const dataKey = `${type.toLowerCase()}Clicks${intervals[0]}` // can be any interval since they all have same amount of total clicks - const totalClicks = pageData[dataKey].reduce((prev: number, cur: TypeClickDatum) => prev + cur.count, 0) + const totalClicks = pageData[dataKey].reduce( + ( + prev: number, + cur: { + date: string + count: number + } + ) => prev + cur.count, + 0 + ) return [type, totalClicks] }) From 9e5132c40a49f680116913c9843713dedd1bb3bf Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Thu, 29 Aug 2024 12:04:16 -0400 Subject: [PATCH 18/37] Updated clicks by job tags to only fetch within given date range --- .../graphql/resolvers/analytics.resolver.ts | 25 +++++++++++++++++++ .../src/graphql/typeDefs/analytics.typedef.ts | 1 + frontend/graphql/queries/analyticsQueries.ts | 2 +- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/backend/src/graphql/resolvers/analytics.resolver.ts b/backend/src/graphql/resolvers/analytics.resolver.ts index f3df0d4..543186c 100644 --- a/backend/src/graphql/resolvers/analytics.resolver.ts +++ b/backend/src/graphql/resolvers/analytics.resolver.ts @@ -435,6 +435,31 @@ const analyticsResolver = { client.release(); } }, + getJobTagRankingByClicksWithDateRange: async (_: any, { startDate, endDate }: any, { dataSources }: any) => { + const { db } = dataSources + const client = await establishConnection(db) + try { + const query = ` + SELECT unnest(tags) AS tag, COUNT(*) AS click_count + FROM job_clicks + JOIN job ON job_clicks.job_id = job.job_id + WHERE tags IS NOT NULL and click_time BETWEEN $1 AND $2 + GROUP BY tag + ORDER BY click_count DESC + ` + const resp = await client.query(query, [startDate, endDate]) + const formattedRows = resp.rows.map((row: any) => ({ + tag: row.tag, + click_count: parseInt(row.click_count), + })) + return formattedRows + } catch (err) { + console.error('Error executing query:', err) + throw new Error('Failed to get job tag ranking by clicks with date range') + } finally { + client.release() + } + }, getApplyClicksForScholar: async ( _: any, { scholarId }: any, diff --git a/backend/src/graphql/typeDefs/analytics.typedef.ts b/backend/src/graphql/typeDefs/analytics.typedef.ts index 82bc6a8..5be61ff 100644 --- a/backend/src/graphql/typeDefs/analytics.typedef.ts +++ b/backend/src/graphql/typeDefs/analytics.typedef.ts @@ -37,6 +37,7 @@ export const analyticsTypeDefs = gql` getNumberOfActiveScholars: Int getNumberOfAllowedScholars: Int getClicksCustomAnalytics(startDate: Date, endDate: Date, interval: String, clickType: String): [CustomAnalytics] + getJobTagRankingByClicksWithDateRange(startDate: Date, endDate: Date): [JobTagRankingByClick] } type Mutation { diff --git a/frontend/graphql/queries/analyticsQueries.ts b/frontend/graphql/queries/analyticsQueries.ts index 6c25c30..7d2b9cf 100644 --- a/frontend/graphql/queries/analyticsQueries.ts +++ b/frontend/graphql/queries/analyticsQueries.ts @@ -404,7 +404,7 @@ export const GET_ANALYTICS_DASHBOARD_DATA = gql` date count } - jobTagRankingsByClicks: getJobTagRankingByClicks { + jobTagRankingsByClicks: getJobTagRankingByClicksWithDateRange(startDate: $startDate, endDate: $endDate) { tag click_count } From 1e8512bc8cf012165c09fb8fc1c858812dc3b475 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Thu, 29 Aug 2024 12:53:29 -0400 Subject: [PATCH 19/37] Use job tag rankings by job info in dashboard --- frontend/pages/Dashboard.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index b8fe706..68570b8 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -37,8 +37,11 @@ function Dashboard() { const scholarsByMajor: [{ major: string; scholar_count: string }] = pageData['scholarsRankedByMajor'] const scholarsByMajorTableBodyData = scholarsByMajor.map((ranking) => [ranking.major, ranking.scholar_count]) + const jobTagRankings: [{ tag: string; job_count: string }] = pageData['jobTagRankings'] + const jobTagRankingsTableBodyData = jobTagRankings.map((ranking) => [ranking.tag, ranking.job_count]) + const jobTagsByClicks: [{ tag: string; click_count: string }] = pageData['jobTagRankingsByClicks'] - const jobTagsTableBodyData = jobTagsByClicks.map((ranking) => [ranking.tag, ranking.click_count]) + const jobTagsByClicksTableBodyData = jobTagsByClicks.map((ranking) => [ranking.tag, ranking.click_count]) const types = ['Job', 'Apply', 'Employer'] const intervals = ['Daily', 'Weekly', 'Monthly', 'Yearly'] @@ -76,7 +79,7 @@ function Dashboard() {
- +

@@ -115,7 +118,7 @@ function Dashboard() {

- +
{typeIntervalClickGroups.map((chart) => ( From b13b653bcd5d5668035b4310875590957c805580 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Thu, 29 Aug 2024 19:58:22 -0400 Subject: [PATCH 20/37] Add visible points to click charts --- frontend/src/components/admin/DashboardClickChart.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/admin/DashboardClickChart.tsx b/frontend/src/components/admin/DashboardClickChart.tsx index 48254b4..00dff6f 100644 --- a/frontend/src/components/admin/DashboardClickChart.tsx +++ b/frontend/src/components/admin/DashboardClickChart.tsx @@ -21,6 +21,7 @@ export default function DashboardClickChart({ data, interval, type }: ClickChart titlePosition: 'none', legend: 'none', backgroundColor: 'transparent', + pointSize: 6, chartArea: { width: '100%', height: '100%', From 4f1a6aca4e45924192e130d408f7eeb75830b00b Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Sun, 1 Sep 2024 11:24:12 -0400 Subject: [PATCH 21/37] Added better loading and error displays --- frontend/pages/Dashboard.tsx | 111 ++++++++++++++------------- frontend/styles/Dashboard.module.css | 1 + 2 files changed, 60 insertions(+), 52 deletions(-) diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index 68570b8..68e7b40 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -15,65 +15,62 @@ function Dashboard() { const [fetchedStartDate, setFetchedStartDate] = useState(startDate) const [fetchedEndDate, setFetchedEndDate] = useState(endDate) - const [getPageData, { data: pageData, loading: pageLoading, error: pageError, called: hasPageFetchedOnce }] = - useLazyQuery(GET_ANALYTICS_DASHBOARD_DATA, { - onCompleted: () => { - setFetchedStartDate(startDate) - setFetchedEndDate(endDate) - }, - }) + const [ + getPageData, + { previousData: previousPageData, data: pageData, loading: pageLoading, error: pageError, called: pageCalled }, + ] = useLazyQuery(GET_ANALYTICS_DASHBOARD_DATA, { + onCompleted: () => { + setFetchedStartDate(startDate) + setFetchedEndDate(endDate) + }, + }) useEffect(() => { - if (!hasPageFetchedOnce) { + if (!pageCalled) { getPageData({ variables: { startDate, endDate } }) } - }, [endDate, getPageData, hasPageFetchedOnce, startDate]) - - if (pageLoading || !hasPageFetchedOnce) return
loading
- if (pageError) { - console.error(pageError.message) - return
error
- } - - const scholarsByMajor: [{ major: string; scholar_count: string }] = pageData['scholarsRankedByMajor'] - const scholarsByMajorTableBodyData = scholarsByMajor.map((ranking) => [ranking.major, ranking.scholar_count]) + }, [endDate, getPageData, pageCalled, startDate]) - const jobTagRankings: [{ tag: string; job_count: string }] = pageData['jobTagRankings'] - const jobTagRankingsTableBodyData = jobTagRankings.map((ranking) => [ranking.tag, ranking.job_count]) + let DashboardContent =
Loading...
+ const currentPageData = pageData ? pageData : previousPageData + if (currentPageData) { + const scholarsByMajor: [{ major: string; scholar_count: string }] = currentPageData['scholarsRankedByMajor'] + const scholarsByMajorTableBodyData = scholarsByMajor.map((ranking) => [ranking.major, ranking.scholar_count]) - const jobTagsByClicks: [{ tag: string; click_count: string }] = pageData['jobTagRankingsByClicks'] - const jobTagsByClicksTableBodyData = jobTagsByClicks.map((ranking) => [ranking.tag, ranking.click_count]) + const jobTagRankings: [{ tag: string; job_count: string }] = currentPageData['jobTagRankings'] + const jobTagRankingsTableBodyData = jobTagRankings.map((ranking) => [ranking.tag, ranking.job_count]) - const types = ['Job', 'Apply', 'Employer'] - const intervals = ['Daily', 'Weekly', 'Monthly', 'Yearly'] - const typeIntervalClickGroups = types.flatMap((type) => - intervals.map((interval) => { - return { - type, - interval, - dataKey: `${type.toLowerCase()}Clicks${interval}`, - } - }) - ) + const jobTagsByClicks: [{ tag: string; click_count: string }] = currentPageData['jobTagRankingsByClicks'] + const jobTagsByClicksTableBodyData = jobTagsByClicks.map((ranking) => [ranking.tag, ranking.click_count]) - const typeClickTableBodyData = types.map((type) => { - const dataKey = `${type.toLowerCase()}Clicks${intervals[0]}` // can be any interval since they all have same amount of total clicks - const totalClicks = pageData[dataKey].reduce( - ( - prev: number, - cur: { - date: string - count: number + const types = ['Job', 'Apply', 'Employer'] + const intervals = ['Daily', 'Weekly', 'Monthly', 'Yearly'] + const typeIntervalClickGroups = types.flatMap((type) => + intervals.map((interval) => { + return { + type, + interval, + dataKey: `${type.toLowerCase()}Clicks${interval}`, } - ) => prev + cur.count, - 0 + }) ) - return [type, totalClicks] - }) - return ( - <> - -
+ const typeClickTableBodyData = types.map((type) => { + const dataKey = `${type.toLowerCase()}Clicks${intervals[0]}` // can be any interval + const totalClicks = currentPageData[dataKey].reduce( + ( + prev: number, + cur: { + date: string + count: number + } + ) => prev + cur.count, + 0 + ) + return [type, totalClicks] + }) + + DashboardContent = ( + <>

Scholar and Job Counts

@@ -94,6 +91,7 @@ function Dashboard() { onChange={(e) => setStartDate(e.target.value)} className={styles.dateLimitInput} max={currentDate} + disabled={pageLoading} />
@@ -123,11 +123,18 @@ function Dashboard() {
{typeIntervalClickGroups.map((chart) => (
- +
))}
-
+ + ) + } + + return ( + <> + +
{pageError ?
Error: {pageError.message}
: DashboardContent}
) } diff --git a/frontend/styles/Dashboard.module.css b/frontend/styles/Dashboard.module.css index 624d5c8..de3a8f1 100644 --- a/frontend/styles/Dashboard.module.css +++ b/frontend/styles/Dashboard.module.css @@ -74,6 +74,7 @@ } .dateLimitSubmit { + min-width: 9rem; height: 2.2rem; padding: 0 2rem; cursor: pointer; From a418987b561bcb4f8876d9c7efa77c90215d2b62 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Sun, 1 Sep 2024 21:37:18 -0400 Subject: [PATCH 22/37] Improved loading states in dashboard --- frontend/pages/Dashboard.tsx | 20 ++++- .../src/components/admin/FancySpinner.tsx | 10 +++ frontend/src/components/admin/Spinner.tsx | 12 +++ frontend/styles/Dashboard.module.css | 42 ++++++++++- .../styles/components/FancySpinner.module.css | 74 +++++++++++++++++++ frontend/styles/components/Spinner.module.css | 19 +++++ 6 files changed, 170 insertions(+), 7 deletions(-) create mode 100644 frontend/src/components/admin/FancySpinner.tsx create mode 100644 frontend/src/components/admin/Spinner.tsx create mode 100644 frontend/styles/components/FancySpinner.module.css create mode 100644 frontend/styles/components/Spinner.module.css diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index 68e7b40..1ecd9cf 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -5,6 +5,8 @@ import DashboardClickChart from '../src/components/admin/DashboardClickChart' import Navbar from '../src/components/general/Navbar' import styles from '../styles/Dashboard.module.css' import DashboardTwoItemTable from '../src/components/admin/DashboardTwoItemTable' +import Spinner from '../src/components/admin/Spinner' +import FancySpinner from '../src/components/admin/FancySpinner' function Dashboard() { const currentDateObj = new Date() @@ -30,7 +32,11 @@ function Dashboard() { } }, [endDate, getPageData, pageCalled, startDate]) - let DashboardContent =
Loading...
+ let DashboardContent = ( +
+ +
+ ) const currentPageData = pageData ? pageData : previousPageData if (currentPageData) { const scholarsByMajor: [{ major: string; scholar_count: string }] = currentPageData['scholarsRankedByMajor'] @@ -112,7 +118,7 @@ function Dashboard() { className={styles.dateLimitSubmit} disabled={pageLoading} > - {pageLoading ?
Loading
: 'Get Clicks'} + {pageLoading ? : 'Get Clicks'}
@@ -134,7 +140,15 @@ function Dashboard() { return ( <> -
{pageError ?
Error: {pageError.message}
: DashboardContent}
+
+ {pageError ? ( +
+ Error: {pageError.message} +
+ ) : ( + DashboardContent + )} +
) } diff --git a/frontend/src/components/admin/FancySpinner.tsx b/frontend/src/components/admin/FancySpinner.tsx new file mode 100644 index 0000000..1a0fa28 --- /dev/null +++ b/frontend/src/components/admin/FancySpinner.tsx @@ -0,0 +1,10 @@ +import styles from '../../../styles/components/FancySpinner.module.css' + +type SpinnerProps = { + size?: number + thickness?: number +} + +export default function FancySpinner({ size = 100 }: SpinnerProps) { + return
+} diff --git a/frontend/src/components/admin/Spinner.tsx b/frontend/src/components/admin/Spinner.tsx new file mode 100644 index 0000000..d3cd088 --- /dev/null +++ b/frontend/src/components/admin/Spinner.tsx @@ -0,0 +1,12 @@ +import styles from '../../../styles/components/Spinner.module.css' + +type SpinnerProps = { + size?: number + thickness?: number +} + +export default function Spinner({ size = 80, thickness = 6 }: SpinnerProps) { + return ( +
+ ) +} diff --git a/frontend/styles/Dashboard.module.css b/frontend/styles/Dashboard.module.css index de3a8f1..573013c 100644 --- a/frontend/styles/Dashboard.module.css +++ b/frontend/styles/Dashboard.module.css @@ -7,6 +7,17 @@ margin: 0 auto; } +.center { + position: fixed; + left: 0; + top: 0; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} + .titleRow { display: flex; flex-direction: column; @@ -32,7 +43,6 @@ } .dateLimitInputContainer { - color: #ddd; display: flex; flex-direction: column; align-items: center; @@ -46,12 +56,18 @@ font-size: 0.85rem; } +.dateLimitInput:disabled, +.dateLimitSubmit:disabled, +.dateLimitSubmit:disabled:hover { + cursor: not-allowed; +} + .dateLimitInput, .dateLimitSubmit { padding: 0 1rem; height: 2.2rem; transition-duration: 150ms; - transition-property: border-color; + transition-property: border-color, opacity; } .dateLimitInputLabel { @@ -60,11 +76,17 @@ .dateLimitInput { background-color: transparent; - border: 1px solid #333; + border-width: 1px; + border-style: solid; border-radius: 0.5rem; cursor: text; } +.dateLimitInput, +.dateLimitInput:disabled:hover { + border-color: #333; +} + .dateLimitInput:hover { border-color: #666; } @@ -81,15 +103,27 @@ background-color: #333; border-width: 2px; border-style: solid; - border-color: #555; border-radius: 0.25rem; font-weight: bold; + display: flex; + justify-content: center; + align-items: center; +} + +.dateLimitSubmit, +.dateLimitSubmit:disabled:hover { + border-color: #555; } .dateLimitSubmit:hover { border-color: #888; } +.dateLimitInput:disabled, +.dateLimitSubmit:disabled { + opacity: 0.7; +} + .quickStats { display: grid; grid-template-columns: minmax(0, 1fr); diff --git a/frontend/styles/components/FancySpinner.module.css b/frontend/styles/components/FancySpinner.module.css new file mode 100644 index 0000000..dd9b51b --- /dev/null +++ b/frontend/styles/components/FancySpinner.module.css @@ -0,0 +1,74 @@ +.loader { + transform: rotateZ(45deg); + perspective: 1000px; + border-radius: 50%; + width: 48px; + height: 48px; + color: #fff; +} + +.loader:before, +.loader:after { + content: ''; + display: block; + position: absolute; + top: 0; + left: 0; + width: inherit; + height: inherit; + border-radius: 50%; + transform: rotateX(70deg); + animation: 1s spin linear infinite; +} + +.loader:after { + color: #999; + transform: rotateY(70deg); + animation-delay: 0.4s; +} + +@keyframes rotate { + 0% { + transform: translate(-50%, -50%) rotateZ(0deg); + } + 100% { + transform: translate(-50%, -50%) rotateZ(360deg); + } +} + +@keyframes rotateccw { + 0% { + transform: translate(-50%, -50%) rotate(0deg); + } + 100% { + transform: translate(-50%, -50%) rotate(-360deg); + } +} + +@keyframes spin { + 0%, + 100% { + box-shadow: 0.5em 0px 0 0px currentcolor; + } + 12% { + box-shadow: 0.5em 0.5em 0 0 currentcolor; + } + 25% { + box-shadow: 0 0.5em 0 0px currentcolor; + } + 37% { + box-shadow: -0.5em 0.5em 0 0 currentcolor; + } + 50% { + box-shadow: -0.5em 0 0 0 currentcolor; + } + 62% { + box-shadow: -0.5em -0.5em 0 0 currentcolor; + } + 75% { + box-shadow: 0px -0.5em 0 0 currentcolor; + } + 87% { + box-shadow: 0.5em -0.5em 0 0 currentcolor; + } +} diff --git a/frontend/styles/components/Spinner.module.css b/frontend/styles/components/Spinner.module.css new file mode 100644 index 0000000..29a2155 --- /dev/null +++ b/frontend/styles/components/Spinner.module.css @@ -0,0 +1,19 @@ +.loader { + width: 48px; + height: 48px; + border: 5px solid #fff; + border-bottom-color: transparent; + border-radius: 50%; + display: inline-block; + box-sizing: border-box; + animation: rotation 1s linear infinite; +} + +@keyframes rotation { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} From cdc656bdb6f3fbb7870ae9b916d48864788c6594 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Sun, 1 Sep 2024 22:08:54 -0400 Subject: [PATCH 23/37] Added more general/static counts to dashboard Also fixed small code stinks --- frontend/graphql/queries/analyticsQueries.ts | 16 +++++ frontend/pages/Dashboard.tsx | 58 ++++++++++++++++--- .../src/components/admin/FancySpinner.tsx | 1 - 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/frontend/graphql/queries/analyticsQueries.ts b/frontend/graphql/queries/analyticsQueries.ts index 7d2b9cf..a7d9317 100644 --- a/frontend/graphql/queries/analyticsQueries.ts +++ b/frontend/graphql/queries/analyticsQueries.ts @@ -412,9 +412,25 @@ export const GET_ANALYTICS_DASHBOARD_DATA = gql` tag job_count } + jobLocationRankings: getJobLocationRanking { + location + job_count + } + jobDeadlineRankingsByMonth: getJobDeadlineRankingByMonth { + month + job_count + } + daysSinceLastJobPostByEmployer: getNumDaysSinceLastJobPostByEmployer { + employerName + days_since_last_post + } scholarsRankedByMajor: getScholarsRankedByMajor { major scholar_count } + scholarsRankedByYear: getScholarsRankedByYear { + year + scholar_count + } } ` diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index 1ecd9cf..aa2641f 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -8,7 +8,7 @@ import DashboardTwoItemTable from '../src/components/admin/DashboardTwoItemTable import Spinner from '../src/components/admin/Spinner' import FancySpinner from '../src/components/admin/FancySpinner' -function Dashboard() { +export default function Dashboard() { const currentDateObj = new Date() const currentDate = currentDateObj.toISOString().split('T')[0] const currentYear = currentDateObj.getFullYear() @@ -39,11 +39,33 @@ function Dashboard() { ) const currentPageData = pageData ? pageData : previousPageData if (currentPageData) { + const jobTagRankings: [{ tag: string; job_count: string }] = currentPageData['jobTagRankings'] + const jobTagRankingsTableBodyData = jobTagRankings.map((ranking) => [ranking.tag, ranking.job_count]) + + const jobLocationRankings: [{ location: string; job_count: string }] = currentPageData['jobLocationRankings'] + const jobLocationRankingsTableBodyData = jobLocationRankings.map((ranking) => [ranking.location, ranking.job_count]) + + const jobDeadlineRankingsByMonth: [{ month: string; job_count: string }] = + currentPageData['jobDeadlineRankingsByMonth'] + const jobDeadlineRankingsByMonthTableBodyData = jobDeadlineRankingsByMonth.map((ranking) => [ + ranking.month, + ranking.job_count, + ]) + + const daysSinceLastJobPostByEmployer: [{ employerName: string; days_since_last_post: string }] = + currentPageData['daysSinceLastJobPostByEmployer'] + const daysSinceLastJobPostByEmployerTableBodyData = daysSinceLastJobPostByEmployer.map((ranking) => [ + ranking.employerName, + ranking.days_since_last_post, + ]) + + daysSinceLastJobPostByEmployer + const scholarsByMajor: [{ major: string; scholar_count: string }] = currentPageData['scholarsRankedByMajor'] const scholarsByMajorTableBodyData = scholarsByMajor.map((ranking) => [ranking.major, ranking.scholar_count]) - const jobTagRankings: [{ tag: string; job_count: string }] = currentPageData['jobTagRankings'] - const jobTagRankingsTableBodyData = jobTagRankings.map((ranking) => [ranking.tag, ranking.job_count]) + const scholarsByYear: [{ year: string; scholar_count: string }] = currentPageData['scholarsRankedByYear'] + const scholarsByYearTableBodyData = scholarsByYear.map((ranking) => [ranking.year, ranking.scholar_count]) const jobTagsByClicks: [{ tag: string; click_count: string }] = currentPageData['jobTagRankingsByClicks'] const jobTagsByClicksTableBodyData = jobTagsByClicks.map((ranking) => [ranking.tag, ranking.click_count]) @@ -78,11 +100,35 @@ function Dashboard() { DashboardContent = ( <>
-

Scholar and Job Counts

+

General Counts

- + + + + +

@@ -152,5 +198,3 @@ function Dashboard() { ) } - -export default Dashboard diff --git a/frontend/src/components/admin/FancySpinner.tsx b/frontend/src/components/admin/FancySpinner.tsx index 1a0fa28..6ea8d08 100644 --- a/frontend/src/components/admin/FancySpinner.tsx +++ b/frontend/src/components/admin/FancySpinner.tsx @@ -2,7 +2,6 @@ import styles from '../../../styles/components/FancySpinner.module.css' type SpinnerProps = { size?: number - thickness?: number } export default function FancySpinner({ size = 100 }: SpinnerProps) { From 574118bad13e5154d280d9fcfc047dc382308ca7 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Sun, 1 Sep 2024 23:14:44 -0400 Subject: [PATCH 24/37] Added click count cards for new categories (needs date range) on dashboard --- frontend/graphql/queries/analyticsQueries.ts | 76 +++++++----- frontend/pages/Dashboard.tsx | 108 ++++++++++++++---- .../admin/DashboardTwoItemTable.tsx | 19 ++- 3 files changed, 148 insertions(+), 55 deletions(-) diff --git a/frontend/graphql/queries/analyticsQueries.ts b/frontend/graphql/queries/analyticsQueries.ts index a7d9317..a1d9aaa 100644 --- a/frontend/graphql/queries/analyticsQueries.ts +++ b/frontend/graphql/queries/analyticsQueries.ts @@ -296,6 +296,54 @@ export const GET_CLICKS_CUSTOM_ANALYTICS = gql` export const GET_ANALYTICS_DASHBOARD_DATA = gql` query GetAnalyticsDashboardData($startDate: Date!, $endDate: Date!) { + jobTagsRankedByJobCount: getJobTagRanking { + tag + job_count + } + jobLocationsRankedByJobCount: getJobLocationRanking { + location + job_count + } + jobDeadlinesAsMonthRankedByJobCount: getJobDeadlineRankingByMonth { + month + job_count + } + daysSinceLastJobPostByEmployer: getNumDaysSinceLastJobPostByEmployer { + employerName + days_since_last_post + } + scholarsRankedByMajor: getScholarsRankedByMajor { + major + scholar_count + } + scholarsRankedByYear: getScholarsRankedByYear { + year + scholar_count + } + jobTagClicks: getJobTagRankingByClicksWithDateRange(startDate: $startDate, endDate: $endDate) { + tag + click_count + } + employerJobPostingClicks: getEmployerJobPostingsRanking { + employerName + job_posting_click_count + } + scholarClicksBySchool: getScholarClicksBySchool { + school + scholar_click_count + } + scholarJobClicks: getScholarJobClicksRanked { + scholarName + job_count + } + scholarApplyClicks: getScholarApplyClicksRanked { + scholarName + apply_count + } + scholarEmployerClicks: getScholarEmployerClicksRanked { + scholarName + employer_count + } jobClicksDaily: getClicksCustomAnalytics( startDate: $startDate endDate: $endDate @@ -404,33 +452,5 @@ export const GET_ANALYTICS_DASHBOARD_DATA = gql` date count } - jobTagRankingsByClicks: getJobTagRankingByClicksWithDateRange(startDate: $startDate, endDate: $endDate) { - tag - click_count - } - jobTagRankings: getJobTagRanking { - tag - job_count - } - jobLocationRankings: getJobLocationRanking { - location - job_count - } - jobDeadlineRankingsByMonth: getJobDeadlineRankingByMonth { - month - job_count - } - daysSinceLastJobPostByEmployer: getNumDaysSinceLastJobPostByEmployer { - employerName - days_since_last_post - } - scholarsRankedByMajor: getScholarsRankedByMajor { - major - scholar_count - } - scholarsRankedByYear: getScholarsRankedByYear { - year - scholar_count - } } ` diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index aa2641f..20e4896 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -39,15 +39,22 @@ export default function Dashboard() { ) const currentPageData = pageData ? pageData : previousPageData if (currentPageData) { - const jobTagRankings: [{ tag: string; job_count: string }] = currentPageData['jobTagRankings'] - const jobTagRankingsTableBodyData = jobTagRankings.map((ranking) => [ranking.tag, ranking.job_count]) + const jobTagsRankedByJobCount: [{ tag: string; job_count: string }] = currentPageData['jobTagsRankedByJobCount'] + const jobTagsRankedByJobCountTableBodyData = jobTagsRankedByJobCount.map((ranking) => [ + ranking.tag, + ranking.job_count, + ]) - const jobLocationRankings: [{ location: string; job_count: string }] = currentPageData['jobLocationRankings'] - const jobLocationRankingsTableBodyData = jobLocationRankings.map((ranking) => [ranking.location, ranking.job_count]) + const jobLocationsRankedByJobCount: [{ location: string; job_count: string }] = + currentPageData['jobLocationsRankedByJobCount'] + const jobLocationsRankedByJobCountTableBodyData = jobLocationsRankedByJobCount.map((ranking) => [ + ranking.location, + ranking.job_count, + ]) - const jobDeadlineRankingsByMonth: [{ month: string; job_count: string }] = - currentPageData['jobDeadlineRankingsByMonth'] - const jobDeadlineRankingsByMonthTableBodyData = jobDeadlineRankingsByMonth.map((ranking) => [ + const jobDeadlinesAsMonthRankedByJobCount: [{ month: string; job_count: string }] = + currentPageData['jobDeadlinesAsMonthRankedByJobCount'] + const jobDeadlinesAsMonthRankedByJobCountTableBodyData = jobDeadlinesAsMonthRankedByJobCount.map((ranking) => [ ranking.month, ranking.job_count, ]) @@ -59,20 +66,16 @@ export default function Dashboard() { ranking.days_since_last_post, ]) - daysSinceLastJobPostByEmployer - const scholarsByMajor: [{ major: string; scholar_count: string }] = currentPageData['scholarsRankedByMajor'] const scholarsByMajorTableBodyData = scholarsByMajor.map((ranking) => [ranking.major, ranking.scholar_count]) const scholarsByYear: [{ year: string; scholar_count: string }] = currentPageData['scholarsRankedByYear'] const scholarsByYearTableBodyData = scholarsByYear.map((ranking) => [ranking.year, ranking.scholar_count]) - const jobTagsByClicks: [{ tag: string; click_count: string }] = currentPageData['jobTagRankingsByClicks'] - const jobTagsByClicksTableBodyData = jobTagsByClicks.map((ranking) => [ranking.tag, ranking.click_count]) - - const types = ['Job', 'Apply', 'Employer'] + // Get click counts for each main type of link + const linkTypes = ['Job', 'Apply', 'Employer'] const intervals = ['Daily', 'Weekly', 'Monthly', 'Yearly'] - const typeIntervalClickGroups = types.flatMap((type) => + const linkTypeIntervalClickGroups = linkTypes.flatMap((type) => intervals.map((interval) => { return { type, @@ -81,8 +84,7 @@ export default function Dashboard() { } }) ) - - const typeClickTableBodyData = types.map((type) => { + const linkTypeClicksTableBodyData = linkTypes.map((type) => { const dataKey = `${type.toLowerCase()}Clicks${intervals[0]}` // can be any interval const totalClicks = currentPageData[dataKey].reduce( ( @@ -97,22 +99,59 @@ export default function Dashboard() { return [type, totalClicks] }) + const jobTagClicks: [{ tag: string; click_count: string }] = currentPageData['jobTagClicks'] + const jobTagClicksTableBodyData = jobTagClicks.map((ranking) => [ranking.tag, ranking.click_count]) + + const employerJobPostingClicks: [{ employerName: string; job_posting_click_count: string }] = + currentPageData['employerJobPostingClicks'] + const employerJobPostingClicksTableBodyData = employerJobPostingClicks.map((ranking) => [ + ranking.employerName, + ranking.job_posting_click_count, + ]) + + const scholarClicksBySchool: [{ school: string; scholar_click_count: string }] = + currentPageData['scholarClicksBySchool'] + const scholarClicksBySchoolTableBodyData = scholarClicksBySchool.map((ranking) => [ + ranking.school, + ranking.scholar_click_count, + ]) + + const scholarJobClicks: [{ scholarName: string; job_count: string }] = currentPageData['scholarJobClicks'] + const scholarJobClicksTableBodyData = scholarJobClicks.map((ranking) => [ranking.scholarName, ranking.job_count]) + + const scholarApplyClicks: [{ scholarName: string; apply_count: string }] = currentPageData['scholarApplyClicks'] + const scholarApplyClicksTableBodyData = scholarApplyClicks.map((ranking) => [ + ranking.scholarName, + ranking.apply_count, + ]) + + const scholarEmployerClicks: [{ scholarName: string; employer_count: string }] = + currentPageData['scholarEmployerClicks'] + const scholarEmployerClicksTableBodyData = scholarEmployerClicks.map((ranking) => [ + ranking.scholarName, + ranking.employer_count, + ]) + DashboardContent = ( <>

General Counts

- +
- - + + + + + + +
- {typeIntervalClickGroups.map((chart) => ( + {linkTypeIntervalClickGroups.map((chart) => (
diff --git a/frontend/src/components/admin/DashboardTwoItemTable.tsx b/frontend/src/components/admin/DashboardTwoItemTable.tsx index c096ffc..63aa30a 100644 --- a/frontend/src/components/admin/DashboardTwoItemTable.tsx +++ b/frontend/src/components/admin/DashboardTwoItemTable.tsx @@ -17,12 +17,21 @@ export default function DashboardTwoItemTable({ firstHeading, secondHeading, dat - {data.map((row) => ( - - {row[0]} - {row[1]} + {data.length ? ( + data.map((row) => ( + + {row[0]} + {row[1]} + + )) + ) : ( + + + No Data + + - ))} + )}
From 1cc7f421d75733feb0a55d116e704e1b4ed1a9fe Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Sun, 1 Sep 2024 23:20:04 -0400 Subject: [PATCH 25/37] Standardized italic 'no data' messages in dashboard --- frontend/src/components/admin/DashboardTwoItemTable.tsx | 4 +--- frontend/styles/components/DashboardClickChart.module.css | 1 + frontend/styles/components/DashboardTwoItemTable.module.css | 4 ++++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/admin/DashboardTwoItemTable.tsx b/frontend/src/components/admin/DashboardTwoItemTable.tsx index 63aa30a..dd34633 100644 --- a/frontend/src/components/admin/DashboardTwoItemTable.tsx +++ b/frontend/src/components/admin/DashboardTwoItemTable.tsx @@ -26,9 +26,7 @@ export default function DashboardTwoItemTable({ firstHeading, secondHeading, dat )) ) : ( - - No Data - + No Data )} diff --git a/frontend/styles/components/DashboardClickChart.module.css b/frontend/styles/components/DashboardClickChart.module.css index 0ec0669..d6f4f1b 100644 --- a/frontend/styles/components/DashboardClickChart.module.css +++ b/frontend/styles/components/DashboardClickChart.module.css @@ -33,4 +33,5 @@ .noDataMessage { padding-bottom: 1rem; + font-style: italic; } diff --git a/frontend/styles/components/DashboardTwoItemTable.module.css b/frontend/styles/components/DashboardTwoItemTable.module.css index b10742b..fcf5569 100644 --- a/frontend/styles/components/DashboardTwoItemTable.module.css +++ b/frontend/styles/components/DashboardTwoItemTable.module.css @@ -50,3 +50,7 @@ .dataTable tbody tr:first-child { padding-top: 0; } + +.dataTable .noDataCell { + font-style: italic; +} From edc3f6d7851c20bca3a6be354b6ca21fed4961ca Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Tue, 3 Sep 2024 14:56:01 -0400 Subject: [PATCH 26/37] Added resolvers for all date ranged dashboard queries --- .../graphql/resolvers/analytics.resolver.ts | 142 ++++++++++++++++++ .../src/graphql/typeDefs/analytics.typedef.ts | 7 +- frontend/graphql/queries/analyticsQueries.ts | 10 +- 3 files changed, 153 insertions(+), 6 deletions(-) diff --git a/backend/src/graphql/resolvers/analytics.resolver.ts b/backend/src/graphql/resolvers/analytics.resolver.ts index 543186c..2a8af6e 100644 --- a/backend/src/graphql/resolvers/analytics.resolver.ts +++ b/backend/src/graphql/resolvers/analytics.resolver.ts @@ -163,6 +163,35 @@ const analyticsResolver = { } }, + getScholarApplyClicksRankedWithDateRange: async (_: any, { startDate, endDate }: any, { dataSources }: any) => { + const { db } = dataSources + const client = await establishConnection(db) + try { + const query = ` + SELECT scholar.scholar_id, scholar.name, scholar.email, COUNT(*) AS clicks + FROM apply_clicks + JOIN scholar ON apply_clicks.scholar_id = scholar.scholar_id + WHERE apply_clicks.click_time BETWEEN $1 AND $2 + GROUP BY scholar.scholar_id, scholar.name, scholar.email + ORDER BY clicks DESC + ` + const resp = await client.query(query, [startDate, endDate]) + console.log(resp.rows) + const formattedRows = resp.rows.map((row: any) => ({ + scholarId: row.scholar_id, + apply_count: parseInt(row.clicks), + scholarName: row.name, + scholarEmail: row.email, + })) + return formattedRows + } catch (err) { + console.error(err) + throw new Error('Failed to fetch scholar job clicks with date range') + } finally { + client.release() + } + }, + getScholarJobClicksRanked: async ( _: any, args: any, @@ -195,6 +224,35 @@ const analyticsResolver = { } }, + getScholarJobClicksRankedWithDateRange: async (_: any, { startDate, endDate }: any, { dataSources }: any) => { + const { db } = dataSources + const client = await establishConnection(db) + try { + const query = ` + SELECT scholar.scholar_id, scholar.name, scholar.email, COUNT(*) AS clicks + FROM job_clicks + JOIN scholar ON job_clicks.scholar_id = scholar.scholar_id + WHERE job_clicks.click_time BETWEEN $1 AND $2 + GROUP BY scholar.scholar_id, scholar.name, scholar.email + ORDER BY clicks DESC + ` + const resp = await client.query(query, [startDate, endDate]) + console.log(resp.rows) + const formattedRows = resp.rows.map((row: any) => ({ + scholarId: row.scholar_id, + job_count: parseInt(row.clicks), + scholarName: row.name, + scholarEmail: row.email, + })) + return formattedRows + } catch (err) { + console.error(err) + throw new Error('Failed to fetch scholar job clicks with date range') + } finally { + client.release() + } + }, + getScholarClicksBySchool: async ( _: any, args: any, @@ -224,6 +282,32 @@ const analyticsResolver = { } }, + getScholarClicksBySchoolWithDateRange: async (_: any, { startDate, endDate }: any, { dataSources }: any) => { + const { db } = dataSources + const client = await establishConnection(db) + try { + const query = ` + SELECT school, COUNT(*) AS scholar_click_count + FROM job_clicks JOIN scholar ON job_clicks.scholar_id = scholar.scholar_id + WHERE job_clicks.click_time BETWEEN $1 AND $2 + GROUP BY school + ORDER BY scholar_click_count DESC + ` + const resp = await client.query(query, [startDate, endDate]) + console.log(resp.rows) + const formattedRows = resp.rows.map((row: any) => ({ + school: row.school, + scholar_click_count: parseInt(row.scholar_click_count), + })) + return formattedRows + } catch (err) { + console.error('Error executing query:', err) + throw new Error('Failed to get scholar clicks by school with date range') + } finally { + client.release() + } + }, + getScholarEmployerClicksRanked: async ( _: any, args: any, @@ -256,6 +340,35 @@ const analyticsResolver = { } }, + getScholarEmployerClicksRankedWithDateRange: async (_: any, { startDate, endDate }: any, { dataSources }: any) => { + const { db } = dataSources + const client = await establishConnection(db) + try { + const query = ` + SELECT scholar.scholar_id, scholar.name, scholar.email, COUNT(*) AS clicks + FROM employer_clicks + JOIN scholar ON employer_clicks.scholar_id = scholar.scholar_id + WHERE employer_clicks.click_time BETWEEN $1 AND $2 + GROUP BY scholar.scholar_id, scholar.name, scholar.email + ORDER BY clicks DESC + ` + const resp = await client.query(query, [startDate, endDate]) + console.log(resp.rows) + const formattedRows = resp.rows.map((row: any) => ({ + scholarId: row.scholar_id, + employer_count: parseInt(row.clicks), + scholarName: row.name, + scholarEmail: row.email, + })) + return formattedRows + } catch (err) { + console.error(err) + throw new Error('Failed to fetch scholar employer clicks with date range') + } finally { + client.release() + } + }, + getJobClicks: async (_: any, args: any, { dataSources }: any) => { const { db } = dataSources; const client = await establishConnection(db); @@ -435,6 +548,7 @@ const analyticsResolver = { client.release(); } }, + getJobTagRankingByClicksWithDateRange: async (_: any, { startDate, endDate }: any, { dataSources }: any) => { const { db } = dataSources const client = await establishConnection(db) @@ -460,6 +574,7 @@ const analyticsResolver = { client.release() } }, + getApplyClicksForScholar: async ( _: any, { scholarId }: any, @@ -731,6 +846,33 @@ const analyticsResolver = { } }, + getEmployerJobPostingsRankingWithDateRange: async (_: any, { startDate, endDate }: any, { dataSources }: any) => { + const { db } = dataSources + const client = await establishConnection(db) + try { + const query = ` + SELECT employer.name, employer.employer_id, COUNT(*) AS job_click_count + FROM job JOIN job_clicks ON job.job_id = job_clicks.job_id JOIN employer ON job.employer_id = employer.employer_id + WHERE live = true AND job_clicks.click_time BETWEEN $1 AND $2 + GROUP BY employer.employer_id + ORDER BY job_click_count DESC + ` + const resp = await client.query(query, [startDate, endDate]) + console.log(resp.rows) + const formattedRows = resp.rows.map((row: any) => ({ + employerName: row.name, + employerId: row.employer_id, + job_posting_click_count: parseInt(row.job_click_count), + })) + return formattedRows + } catch (err) { + console.error('Error executing query:', err) + throw new Error('Failed to get employer job postings ranking with date range') + } finally { + client.release() + } + }, + getNumDaysSinceLastJobPostByEmployer: async ( _: any, args: any, diff --git a/backend/src/graphql/typeDefs/analytics.typedef.ts b/backend/src/graphql/typeDefs/analytics.typedef.ts index 5be61ff..04ac308 100644 --- a/backend/src/graphql/typeDefs/analytics.typedef.ts +++ b/backend/src/graphql/typeDefs/analytics.typedef.ts @@ -9,17 +9,23 @@ export const analyticsTypeDefs = gql` getEmployerClicksRanked: [RankedEmployerClick] getJobTagRanking: [JobTagRanking] getJobTagRankingByClicks: [JobTagRankingByClick] + getJobTagRankingByClicksWithDateRange(startDate: Date, endDate: Date): [JobTagRankingByClick] getJobLocationRanking: [JobLocationRanking] getJobDeadlineRankingByMonth: [JobDeadlineRanking] getScholarsRankedByMajor: [MajorRanking] getScholarsRankedByYear: [YearRanking] getPercentageOfScholarsWithAllowedNotifications: Int getScholarApplyClicksRanked: [ApplyClickRank] + getScholarApplyClicksRankedWithDateRange(startDate: Date, endDate: Date): [ApplyClickRank] getScholarJobClicksRanked: [JobClickRank] + getScholarJobClicksRankedWithDateRange(startDate: Date, endDate: Date): [JobClickRank] getScholarEmployerClicksRanked: [EmployerClickRank] + getScholarEmployerClicksRankedWithDateRange(startDate: Date, endDate: Date): [EmployerClickRank] getJobClicksRankedByApply: [RankedJobClick] getScholarClicksBySchool: [ScholarClicksBySchool] + getScholarClicksBySchoolWithDateRange(startDate: Date, endDate: Date): [ScholarClicksBySchool] getEmployerJobPostingsRanking: [EmployerJobPostingRank] + getEmployerJobPostingsRankingWithDateRange(startDate: Date, endDate: Date): [EmployerJobPostingRank] getNumDaysSinceLastJobPostByEmployer: [EmployerLastJobPost] getMostPopularJobTagsByEmployer: [EmployerJobTagRanking] getJobClicksForScholar(scholarId: Int): [ScholarJobClicks] @@ -37,7 +43,6 @@ export const analyticsTypeDefs = gql` getNumberOfActiveScholars: Int getNumberOfAllowedScholars: Int getClicksCustomAnalytics(startDate: Date, endDate: Date, interval: String, clickType: String): [CustomAnalytics] - getJobTagRankingByClicksWithDateRange(startDate: Date, endDate: Date): [JobTagRankingByClick] } type Mutation { diff --git a/frontend/graphql/queries/analyticsQueries.ts b/frontend/graphql/queries/analyticsQueries.ts index a1d9aaa..a96c796 100644 --- a/frontend/graphql/queries/analyticsQueries.ts +++ b/frontend/graphql/queries/analyticsQueries.ts @@ -324,23 +324,23 @@ export const GET_ANALYTICS_DASHBOARD_DATA = gql` tag click_count } - employerJobPostingClicks: getEmployerJobPostingsRanking { + employerJobPostingClicks: getEmployerJobPostingsRankingWithDateRange(startDate: $startDate, endDate: $endDate) { employerName job_posting_click_count } - scholarClicksBySchool: getScholarClicksBySchool { + scholarClicksBySchool: getScholarClicksBySchoolWithDateRange(startDate: $startDate, endDate: $endDate) { school scholar_click_count } - scholarJobClicks: getScholarJobClicksRanked { + scholarJobClicks: getScholarJobClicksRankedWithDateRange(startDate: $startDate, endDate: $endDate) { scholarName job_count } - scholarApplyClicks: getScholarApplyClicksRanked { + scholarApplyClicks: getScholarApplyClicksRankedWithDateRange(startDate: $startDate, endDate: $endDate) { scholarName apply_count } - scholarEmployerClicks: getScholarEmployerClicksRanked { + scholarEmployerClicks: getScholarEmployerClicksRankedWithDateRange(startDate: $startDate, endDate: $endDate) { scholarName employer_count } From b54292cc2b39fbc5ea091bbfd9a814760e1fe07b Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Tue, 3 Sep 2024 16:15:29 -0400 Subject: [PATCH 27/37] Refactor dashboard in to components --- frontend/pages/Dashboard.tsx | 78 ++++++------- .../admin/DashboardDateLimitInput.tsx | 22 ++++ .../admin/DashboardDateLimitSubmit.tsx | 23 ++++ .../admin/DashboardErrorMessage.tsx | 11 ++ .../components/admin/DashboardTitleRow.tsx | 16 +++ frontend/src/components/admin/Spinner.tsx | 10 +- frontend/styles/Dashboard.module.css | 104 ------------------ .../DashboardDateLimitComponents.module.css | 81 ++++++++++++++ .../components/DashboardTitleRow.module.css | 23 ++++ 9 files changed, 212 insertions(+), 156 deletions(-) create mode 100644 frontend/src/components/admin/DashboardDateLimitInput.tsx create mode 100644 frontend/src/components/admin/DashboardDateLimitSubmit.tsx create mode 100644 frontend/src/components/admin/DashboardErrorMessage.tsx create mode 100644 frontend/src/components/admin/DashboardTitleRow.tsx create mode 100644 frontend/styles/components/DashboardDateLimitComponents.module.css create mode 100644 frontend/styles/components/DashboardTitleRow.module.css diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index 20e4896..0398891 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -2,11 +2,14 @@ import { useLazyQuery } from '@apollo/client' import { useEffect, useState } from 'react' import { GET_ANALYTICS_DASHBOARD_DATA } from '../graphql/queries/analyticsQueries' import DashboardClickChart from '../src/components/admin/DashboardClickChart' -import Navbar from '../src/components/general/Navbar' -import styles from '../styles/Dashboard.module.css' +import DashboardDateLimitInput from '../src/components/admin/DashboardDateLimitInput' +import DashboardDateLimitSubmit from '../src/components/admin/DashboardDateLimitSubmit' +import DashboardErrorMessage from '../src/components/admin/DashboardErrorMessage' +import DashboardTitleRow from '../src/components/admin/DashboardTitleRow' import DashboardTwoItemTable from '../src/components/admin/DashboardTwoItemTable' -import Spinner from '../src/components/admin/Spinner' import FancySpinner from '../src/components/admin/FancySpinner' +import Navbar from '../src/components/general/Navbar' +import styles from '../styles/Dashboard.module.css' export default function Dashboard() { const currentDateObj = new Date() @@ -37,7 +40,7 @@ export default function Dashboard() {

) - const currentPageData = pageData ? pageData : previousPageData + const currentPageData = pageData ? pageData : previousPageData // shows old data while new date range is being fetched if (currentPageData) { const jobTagsRankedByJobCount: [{ tag: string; job_count: string }] = currentPageData['jobTagsRankedByJobCount'] const jobTagsRankedByJobCountTableBodyData = jobTagsRankedByJobCount.map((ranking) => [ @@ -134,9 +137,7 @@ export default function Dashboard() { DashboardContent = ( <> -
-

General Counts

-
+
-
-

- Click Counts from {fetchedStartDate} to {fetchedEndDate} -

-
- - - -
-
+ getPageData({ variables: { startDate, endDate } })} + loading={pageLoading} + disabled={pageLoading} + > + Get Clicks + + + } + />
@@ -236,13 +230,11 @@ export default function Dashboard() { data={scholarEmployerClicksTableBodyData} />
-
- {linkTypeIntervalClickGroups.map((chart) => ( -
- -
- ))} -
+ {linkTypeIntervalClickGroups.map((chart) => ( +
+ +
+ ))} ) } @@ -251,13 +243,7 @@ export default function Dashboard() { <>
- {pageError ? ( -
- Error: {pageError.message} -
- ) : ( - DashboardContent - )} + {pageError ? : DashboardContent}
) diff --git a/frontend/src/components/admin/DashboardDateLimitInput.tsx b/frontend/src/components/admin/DashboardDateLimitInput.tsx new file mode 100644 index 0000000..5bceafb --- /dev/null +++ b/frontend/src/components/admin/DashboardDateLimitInput.tsx @@ -0,0 +1,22 @@ +import styles from '../../../styles/components/DashboardDateLimitComponents.module.css' + +type DashboardDateLimitInputProps = { + label: string + value: string + onChange: (e: React.ChangeEvent) => void + [extraProps: string]: any +} + +export default function DashboardDateLimitInput({ + label, + value, + onChange, + ...extraProps +}: DashboardDateLimitInputProps) { + return ( + + ) +} diff --git a/frontend/src/components/admin/DashboardDateLimitSubmit.tsx b/frontend/src/components/admin/DashboardDateLimitSubmit.tsx new file mode 100644 index 0000000..73f1e91 --- /dev/null +++ b/frontend/src/components/admin/DashboardDateLimitSubmit.tsx @@ -0,0 +1,23 @@ +import { MouseEventHandler, ReactNode } from 'react' +import styles from '../../../styles/components/DashboardDateLimitComponents.module.css' +import Spinner from './Spinner' + +type DashboardDateLimitSubmitProps = { + onClick: MouseEventHandler + loading: boolean + children: ReactNode + [extraProps: string]: any +} + +export default function DashboardDateLimitSubmit({ + onClick, + loading, + children, + ...extraProps +}: DashboardDateLimitSubmitProps) { + return ( + + ) +} diff --git a/frontend/src/components/admin/DashboardErrorMessage.tsx b/frontend/src/components/admin/DashboardErrorMessage.tsx new file mode 100644 index 0000000..ef4efc9 --- /dev/null +++ b/frontend/src/components/admin/DashboardErrorMessage.tsx @@ -0,0 +1,11 @@ +type DashboardErrorMessageProps = { + message: string +} + +export default function DashboardErrorMessage({ message }: DashboardErrorMessageProps) { + return ( +
+ Error: {message} +
+ ) +} diff --git a/frontend/src/components/admin/DashboardTitleRow.tsx b/frontend/src/components/admin/DashboardTitleRow.tsx new file mode 100644 index 0000000..be963cb --- /dev/null +++ b/frontend/src/components/admin/DashboardTitleRow.tsx @@ -0,0 +1,16 @@ +import { ReactElement } from 'react' +import styles from '../../../styles/components/DashboardTitleRow.module.css' + +type DashboardTitleRowProps = { + title: string + rightComponent?: ReactElement +} + +export default function DashboardTitleRow({ title, rightComponent = <> }: DashboardTitleRowProps) { + return ( +
+

{title}

+ {rightComponent} +
+ ) +} diff --git a/frontend/src/components/admin/Spinner.tsx b/frontend/src/components/admin/Spinner.tsx index d3cd088..e7aa0d7 100644 --- a/frontend/src/components/admin/Spinner.tsx +++ b/frontend/src/components/admin/Spinner.tsx @@ -1,12 +1,10 @@ import styles from '../../../styles/components/Spinner.module.css' type SpinnerProps = { - size?: number - thickness?: number + size?: string + thickness?: string } -export default function Spinner({ size = 80, thickness = 6 }: SpinnerProps) { - return ( -
- ) +export default function Spinner({ size = '1em', thickness = '0.2em' }: SpinnerProps) { + return
} diff --git a/frontend/styles/Dashboard.module.css b/frontend/styles/Dashboard.module.css index 573013c..1929157 100644 --- a/frontend/styles/Dashboard.module.css +++ b/frontend/styles/Dashboard.module.css @@ -18,22 +18,6 @@ align-items: center; } -.titleRow { - display: flex; - flex-direction: column; - justify-content: start; - align-items: center; - gap: 2rem; - margin-bottom: 2rem; -} - -.mainTitle { - margin: 0; - font-size: 1.8rem; - font-weight: bold; - text-align: left; -} - .dateLimitsForm { display: flex; flex-flow: wrap; @@ -42,88 +26,6 @@ gap: 1rem; } -.dateLimitInputContainer { - display: flex; - flex-direction: column; - align-items: center; -} - -.dateLimitInputLabel, -.dateLimitInput, -.dateLimitSubmit { - color: inherit; - font-family: inherit; - font-size: 0.85rem; -} - -.dateLimitInput:disabled, -.dateLimitSubmit:disabled, -.dateLimitSubmit:disabled:hover { - cursor: not-allowed; -} - -.dateLimitInput, -.dateLimitSubmit { - padding: 0 1rem; - height: 2.2rem; - transition-duration: 150ms; - transition-property: border-color, opacity; -} - -.dateLimitInputLabel { - margin-bottom: 0.25rem; -} - -.dateLimitInput { - background-color: transparent; - border-width: 1px; - border-style: solid; - border-radius: 0.5rem; - cursor: text; -} - -.dateLimitInput, -.dateLimitInput:disabled:hover { - border-color: #333; -} - -.dateLimitInput:hover { - border-color: #666; -} - -.dateLimitInput::-webkit-calendar-picker-indicator { - cursor: pointer; -} - -.dateLimitSubmit { - min-width: 9rem; - height: 2.2rem; - padding: 0 2rem; - cursor: pointer; - background-color: #333; - border-width: 2px; - border-style: solid; - border-radius: 0.25rem; - font-weight: bold; - display: flex; - justify-content: center; - align-items: center; -} - -.dateLimitSubmit, -.dateLimitSubmit:disabled:hover { - border-color: #555; -} - -.dateLimitSubmit:hover { - border-color: #888; -} - -.dateLimitInput:disabled, -.dateLimitSubmit:disabled { - opacity: 0.7; -} - .quickStats { display: grid; grid-template-columns: minmax(0, 1fr); @@ -140,12 +42,6 @@ } @media screen and (min-width: 1064px) { - .titleRow { - flex-direction: row; - justify-content: space-between; - align-items: center; - } - .quickStats { grid-template-columns: repeat(2, minmax(0, 1fr)); } diff --git a/frontend/styles/components/DashboardDateLimitComponents.module.css b/frontend/styles/components/DashboardDateLimitComponents.module.css new file mode 100644 index 0000000..ab90714 --- /dev/null +++ b/frontend/styles/components/DashboardDateLimitComponents.module.css @@ -0,0 +1,81 @@ +.dateLimitInputContainer { + display: flex; + flex-direction: column; + align-items: center; +} + +.dateLimitInputLabel, +.dateLimitInput, +.dateLimitSubmit { + color: inherit; + font-family: inherit; + font-size: 0.85rem; +} + +.dateLimitInput:disabled, +.dateLimitSubmit:disabled, +.dateLimitSubmit:disabled:hover { + cursor: not-allowed; +} + +.dateLimitInput, +.dateLimitSubmit { + padding: 0 1rem; + height: 2.2rem; + transition-duration: 150ms; + transition-property: border-color, opacity; +} + +.dateLimitInputLabel { + margin-bottom: 0.25rem; +} + +.dateLimitInput { + background-color: transparent; + border-width: 1px; + border-style: solid; + border-radius: 0.5rem; + cursor: text; +} + +.dateLimitInput, +.dateLimitInput:disabled:hover { + border-color: #333; +} + +.dateLimitInput:hover { + border-color: #666; +} + +.dateLimitInput::-webkit-calendar-picker-indicator { + cursor: pointer; +} + +.dateLimitSubmit { + min-width: 9rem; + height: 2.2rem; + padding: 0 2rem; + cursor: pointer; + background-color: #333; + border-width: 2px; + border-style: solid; + border-radius: 0.25rem; + font-weight: bold; + display: flex; + justify-content: center; + align-items: center; +} + +.dateLimitSubmit, +.dateLimitSubmit:disabled:hover { + border-color: #555; +} + +.dateLimitSubmit:hover { + border-color: #888; +} + +.dateLimitInput:disabled, +.dateLimitSubmit:disabled { + opacity: 0.7; +} diff --git a/frontend/styles/components/DashboardTitleRow.module.css b/frontend/styles/components/DashboardTitleRow.module.css new file mode 100644 index 0000000..c53a923 --- /dev/null +++ b/frontend/styles/components/DashboardTitleRow.module.css @@ -0,0 +1,23 @@ +.titleRow { + display: flex; + flex-direction: column; + justify-content: start; + align-items: center; + gap: 2rem; + margin-bottom: 2rem; +} + +.title { + margin: 0; + font-size: 1.8rem; + font-weight: bold; + text-align: left; +} + +@media screen and (min-width: 1064px) { + .titleRow { + flex-direction: row; + justify-content: space-between; + align-items: center; + } +} From f1c92348f7fcf91942d85404a2b40f4f1cdc5898 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Tue, 3 Sep 2024 17:19:04 -0400 Subject: [PATCH 28/37] Made dashboard more printable --- frontend/pages/Dashboard.tsx | 4 +- .../components/admin/DashboardClickChart.tsx | 46 ++++++++++++++----- frontend/styles/Dashboard.module.css | 7 +++ .../components/DashboardClickChart.module.css | 18 ++++++++ .../DashboardTwoItemTable.module.css | 1 + 5 files changed, 64 insertions(+), 12 deletions(-) diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index 0398891..5aa5a64 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -241,7 +241,9 @@ export default function Dashboard() { return ( <> - +
+ +
{pageError ? : DashboardContent}
diff --git a/frontend/src/components/admin/DashboardClickChart.tsx b/frontend/src/components/admin/DashboardClickChart.tsx index 00dff6f..978577a 100644 --- a/frontend/src/components/admin/DashboardClickChart.tsx +++ b/frontend/src/components/admin/DashboardClickChart.tsx @@ -1,6 +1,7 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' import styles from '../../../styles/components/DashboardClickChart.module.css' import Chart from 'react-google-charts' +import Image from 'next/image' type ClickChartProps = { data: [ @@ -62,16 +63,39 @@ export default function DashboardClickChart({ data, interval, type }: ClickChart axisTitlesPosition: 'none', } + // Get chart PNG for printing + const [chartWrapper, setChartWrapper] = useState(null) + const [chartImageURI, setChartImageURI] = useState('') + useEffect(() => { + if (chartWrapper !== null) { + setChartImageURI(chartWrapper.getChart().getImageURI()) + } + }, [chartWrapper]) + return ( -
-
{title}
- {data.length ? ( -
- -
- ) : ( -
No data
- )} -
+ <> +
+
{title}
+ {data.length ? ( + <> +
+ setChartWrapper(wrapper)} + /> +
+
+ {`${title} +
+ + ) : ( +
No data
+ )} +
+ ) } diff --git a/frontend/styles/Dashboard.module.css b/frontend/styles/Dashboard.module.css index 1929157..484b6b8 100644 --- a/frontend/styles/Dashboard.module.css +++ b/frontend/styles/Dashboard.module.css @@ -53,3 +53,10 @@ align-items: end; } } + +@media print { + .noPrint, + .dateLimitsForm { + display: none; + } +} diff --git a/frontend/styles/components/DashboardClickChart.module.css b/frontend/styles/components/DashboardClickChart.module.css index d6f4f1b..6bca284 100644 --- a/frontend/styles/components/DashboardClickChart.module.css +++ b/frontend/styles/components/DashboardClickChart.module.css @@ -6,6 +6,7 @@ padding: 2rem 2rem 1rem; border: #333 1px solid; border-radius: 1rem; + break-inside: avoid; } .chartTitle { @@ -35,3 +36,20 @@ padding-bottom: 1rem; font-style: italic; } + +.chartImageContainer { + display: none; + position: relative; + width: 100%; + height: 300px; +} + +@media print { + .googleChartContainer { + display: none; + } + + .chartImageContainer { + display: block; + } +} diff --git a/frontend/styles/components/DashboardTwoItemTable.module.css b/frontend/styles/components/DashboardTwoItemTable.module.css index fcf5569..cea9139 100644 --- a/frontend/styles/components/DashboardTwoItemTable.module.css +++ b/frontend/styles/components/DashboardTwoItemTable.module.css @@ -2,6 +2,7 @@ overflow-x: auto; display: flex; align-items: stretch; + break-inside: avoid; } .dataTable table { From 48227cf4596e2b95be3e7ac8609477bdeb251805 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Tue, 3 Sep 2024 18:23:21 -0400 Subject: [PATCH 29/37] Add error checking for date limits form in dashboard --- frontend/pages/Dashboard.tsx | 85 +++++++++++++------ frontend/styles/Dashboard.module.css | 6 ++ .../DashboardDateLimitComponents.module.css | 1 + 3 files changed, 66 insertions(+), 26 deletions(-) diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index 5aa5a64..8241ee6 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -19,6 +19,7 @@ export default function Dashboard() { const [endDate, setEndDate] = useState(`${currentYear}-12-31`) const [fetchedStartDate, setFetchedStartDate] = useState(startDate) const [fetchedEndDate, setFetchedEndDate] = useState(endDate) + const [dateRangeFormError, setDateRangeFormError] = useState('') const [ getPageData, @@ -35,6 +36,29 @@ export default function Dashboard() { } }, [endDate, getPageData, pageCalled, startDate]) + const hasDateRangeFormError = () => { + setDateRangeFormError('') + + const validDateRegex = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/ + if (!validDateRegex.test(startDate)) { + setDateRangeFormError('Start date is invalid.') + return true + } + if (!validDateRegex.test(endDate)) { + setDateRangeFormError('End date is invalid.') + return true + } + + return false + } + const submitGetPageDataForm = () => { + if (hasDateRangeFormError()) { + return + } + + getPageData({ variables: { startDate, endDate } }) + } + let DashboardContent = (
@@ -173,32 +197,41 @@ export default function Dashboard() { - setStartDate(e.target.value)} - className={styles.dateLimitInput} - max={currentDate} - disabled={pageLoading} - /> - setEndDate(e.target.value)} - className={styles.dateLimitInput} - min={startDate} - max={currentDate} - disabled={pageLoading} - /> - getPageData({ variables: { startDate, endDate } })} - loading={pageLoading} - disabled={pageLoading} - > - Get Clicks - - +
+
+ { + setStartDate(e.target.value) + hasDateRangeFormError() + }} + onFocus={hasDateRangeFormError} + onBlur={hasDateRangeFormError} + className={styles.dateLimitInput} + max={currentDate} + disabled={pageLoading} + /> + { + setEndDate(e.target.value) + hasDateRangeFormError() + }} + onFocus={hasDateRangeFormError} + onBlur={hasDateRangeFormError} + className={styles.dateLimitInput} + min={startDate} + max={currentDate} + disabled={pageLoading} + /> + + Get Clicks + + + {dateRangeFormError ?
{dateRangeFormError}
: null} +
} />
diff --git a/frontend/styles/Dashboard.module.css b/frontend/styles/Dashboard.module.css index 484b6b8..6901c12 100644 --- a/frontend/styles/Dashboard.module.css +++ b/frontend/styles/Dashboard.module.css @@ -26,6 +26,12 @@ gap: 1rem; } +.dateLimitsFormError { + color: #ef8b8b; + padding: 1rem 0; + text-align: center; +} + .quickStats { display: grid; grid-template-columns: minmax(0, 1fr); diff --git a/frontend/styles/components/DashboardDateLimitComponents.module.css b/frontend/styles/components/DashboardDateLimitComponents.module.css index ab90714..a3dd005 100644 --- a/frontend/styles/components/DashboardDateLimitComponents.module.css +++ b/frontend/styles/components/DashboardDateLimitComponents.module.css @@ -35,6 +35,7 @@ border-width: 1px; border-style: solid; border-radius: 0.5rem; + min-width: 9rem; cursor: text; } From eb943618b77831d7a658f2d787ab54d2dda4f1ac Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Tue, 3 Sep 2024 22:03:40 -0400 Subject: [PATCH 30/37] Fixed errors with no img src and duplicated map keys --- frontend/src/components/admin/DashboardClickChart.tsx | 8 +++++--- frontend/src/components/admin/DashboardTwoItemTable.tsx | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/admin/DashboardClickChart.tsx b/frontend/src/components/admin/DashboardClickChart.tsx index 978577a..39b068f 100644 --- a/frontend/src/components/admin/DashboardClickChart.tsx +++ b/frontend/src/components/admin/DashboardClickChart.tsx @@ -88,9 +88,11 @@ export default function DashboardClickChart({ data, interval, type }: ClickChart getChartWrapper={(wrapper) => setChartWrapper(wrapper)} />
-
- {`${title} -
+ {chartImageURI ? ( +
+ {`${title} +
+ ) : null} ) : (
No data
diff --git a/frontend/src/components/admin/DashboardTwoItemTable.tsx b/frontend/src/components/admin/DashboardTwoItemTable.tsx index dd34633..957300a 100644 --- a/frontend/src/components/admin/DashboardTwoItemTable.tsx +++ b/frontend/src/components/admin/DashboardTwoItemTable.tsx @@ -19,7 +19,7 @@ export default function DashboardTwoItemTable({ firstHeading, secondHeading, dat {data.length ? ( data.map((row) => ( - + {row[0]} {row[1]} From 182500d21ffd362da548d402ea5954cfac28f267 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Thu, 5 Sep 2024 14:17:41 -0400 Subject: [PATCH 31/37] Fixed click charts showing wrong data when printing --- frontend/src/components/admin/DashboardClickChart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/admin/DashboardClickChart.tsx b/frontend/src/components/admin/DashboardClickChart.tsx index 39b068f..506f6ce 100644 --- a/frontend/src/components/admin/DashboardClickChart.tsx +++ b/frontend/src/components/admin/DashboardClickChart.tsx @@ -70,7 +70,7 @@ export default function DashboardClickChart({ data, interval, type }: ClickChart if (chartWrapper !== null) { setChartImageURI(chartWrapper.getChart().getImageURI()) } - }, [chartWrapper]) + }, [chartWrapper, data]) return ( <> From 46d3ad4257dc8030b83a010ccf750d24ea29cfbc Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Thu, 5 Sep 2024 14:22:01 -0400 Subject: [PATCH 32/37] Updated printable version of click chart whenever any prop changes --- frontend/src/components/admin/DashboardClickChart.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/admin/DashboardClickChart.tsx b/frontend/src/components/admin/DashboardClickChart.tsx index 506f6ce..5ad64e8 100644 --- a/frontend/src/components/admin/DashboardClickChart.tsx +++ b/frontend/src/components/admin/DashboardClickChart.tsx @@ -70,7 +70,7 @@ export default function DashboardClickChart({ data, interval, type }: ClickChart if (chartWrapper !== null) { setChartImageURI(chartWrapper.getChart().getImageURI()) } - }, [chartWrapper, data]) + }, [chartWrapper, data, interval, type]) return ( <> From 07edebb299b4d0bbad25c6b4fda345df47ea1f5e Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Sun, 8 Sep 2024 17:40:26 -0400 Subject: [PATCH 33/37] Made font color black when printing dashboard --- frontend/styles/Dashboard.module.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/styles/Dashboard.module.css b/frontend/styles/Dashboard.module.css index 6901c12..a1b6be1 100644 --- a/frontend/styles/Dashboard.module.css +++ b/frontend/styles/Dashboard.module.css @@ -61,6 +61,10 @@ } @media print { + .wrapper { + color: black; + } + .noPrint, .dateLimitsForm { display: none; From 2b2fb816c31f62ea09f3d025f49dc0b50615aa05 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Mon, 9 Sep 2024 16:49:05 -0400 Subject: [PATCH 34/37] Made dashboard more compact when printing --- frontend/pages/Dashboard.tsx | 12 +++++++----- frontend/styles/Dashboard.module.css | 8 ++++++++ .../DashboardTwoItemTable.module.css | 18 ++++++++++++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index 8241ee6..c56641e 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -263,11 +263,13 @@ export default function Dashboard() { data={scholarEmployerClicksTableBodyData} />
- {linkTypeIntervalClickGroups.map((chart) => ( -
- -
- ))} +
+ {linkTypeIntervalClickGroups.map((chart) => ( +
+ +
+ ))} +
) } diff --git a/frontend/styles/Dashboard.module.css b/frontend/styles/Dashboard.module.css index a1b6be1..b6719f5 100644 --- a/frontend/styles/Dashboard.module.css +++ b/frontend/styles/Dashboard.module.css @@ -39,6 +39,10 @@ margin-bottom: 3rem; } +.charts { + break-inside: avoid; +} + .chartContainer { margin-bottom: 3rem; } @@ -61,6 +65,10 @@ } @media print { + .quickStats { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .wrapper { color: black; } diff --git a/frontend/styles/components/DashboardTwoItemTable.module.css b/frontend/styles/components/DashboardTwoItemTable.module.css index cea9139..d5b5f98 100644 --- a/frontend/styles/components/DashboardTwoItemTable.module.css +++ b/frontend/styles/components/DashboardTwoItemTable.module.css @@ -32,6 +32,7 @@ .dataTable tbody { border: #333 1px solid; + border-top: none; border-radius: 0 0 1rem 1rem; padding: 1rem; max-height: 300px; @@ -55,3 +56,20 @@ .dataTable .noDataCell { font-style: italic; } + +@media print { + .dataTable thead tr { + padding: 0.5rem; + } + + .dataTable tbody { + max-height: 290px; + padding: 0.5rem; + overflow: hidden; + } + + .dataTable tbody tr { + padding-top: 0.25rem; + padding-bottom: 0.25rem; + } +} From 381abd689016c45a8d456c14f39e1604cb47a4f8 Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Mon, 9 Sep 2024 16:57:15 -0400 Subject: [PATCH 35/37] Fixed dashboard crashing when using browser back button --- frontend/pages/Dashboard.tsx | 1 + frontend/src/components/admin/DashboardClickChart.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index c56641e..db769bf 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -25,6 +25,7 @@ export default function Dashboard() { getPageData, { previousData: previousPageData, data: pageData, loading: pageLoading, error: pageError, called: pageCalled }, ] = useLazyQuery(GET_ANALYTICS_DASHBOARD_DATA, { + fetchPolicy: 'cache-and-network', onCompleted: () => { setFetchedStartDate(startDate) setFetchedEndDate(endDate) diff --git a/frontend/src/components/admin/DashboardClickChart.tsx b/frontend/src/components/admin/DashboardClickChart.tsx index 5ad64e8..a5b6b85 100644 --- a/frontend/src/components/admin/DashboardClickChart.tsx +++ b/frontend/src/components/admin/DashboardClickChart.tsx @@ -68,7 +68,7 @@ export default function DashboardClickChart({ data, interval, type }: ClickChart const [chartImageURI, setChartImageURI] = useState('') useEffect(() => { if (chartWrapper !== null) { - setChartImageURI(chartWrapper.getChart().getImageURI()) + setChartImageURI(chartWrapper.getChart()?.getImageURI()) } }, [chartWrapper, data, interval, type]) From fe92b42c54477f5271bc922e01e2a6b26b6b02dd Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Mon, 9 Sep 2024 17:03:47 -0400 Subject: [PATCH 36/37] Made each section start on new page when printing --- frontend/pages/Dashboard.tsx | 209 ++++++++++++++------------- frontend/styles/Dashboard.module.css | 2 +- 2 files changed, 110 insertions(+), 101 deletions(-) diff --git a/frontend/pages/Dashboard.tsx b/frontend/pages/Dashboard.tsx index db769bf..1aa761b 100644 --- a/frontend/pages/Dashboard.tsx +++ b/frontend/pages/Dashboard.tsx @@ -162,109 +162,118 @@ export default function Dashboard() { DashboardContent = ( <> - -
- - - - - - +
+ +
+ + + + + + +
- -
- { - setStartDate(e.target.value) - hasDateRangeFormError() - }} - onFocus={hasDateRangeFormError} - onBlur={hasDateRangeFormError} - className={styles.dateLimitInput} - max={currentDate} - disabled={pageLoading} - /> - { - setEndDate(e.target.value) - hasDateRangeFormError() - }} - onFocus={hasDateRangeFormError} - onBlur={hasDateRangeFormError} - className={styles.dateLimitInput} - min={startDate} - max={currentDate} - disabled={pageLoading} - /> - - Get Clicks - - - {dateRangeFormError ?
{dateRangeFormError}
: null} -
- } - /> -
- - - - - - - + +
+ { + setStartDate(e.target.value) + hasDateRangeFormError() + }} + onFocus={hasDateRangeFormError} + onBlur={hasDateRangeFormError} + className={styles.dateLimitInput} + max={currentDate} + disabled={pageLoading} + /> + { + setEndDate(e.target.value) + hasDateRangeFormError() + }} + onFocus={hasDateRangeFormError} + onBlur={hasDateRangeFormError} + className={styles.dateLimitInput} + min={startDate} + max={currentDate} + disabled={pageLoading} + /> + + Get Clicks + + + {dateRangeFormError ?
{dateRangeFormError}
: null} +
+ } /> + +
+ + + + + + + +
-
+
{linkTypeIntervalClickGroups.map((chart) => (
diff --git a/frontend/styles/Dashboard.module.css b/frontend/styles/Dashboard.module.css index b6719f5..6972a67 100644 --- a/frontend/styles/Dashboard.module.css +++ b/frontend/styles/Dashboard.module.css @@ -39,7 +39,7 @@ margin-bottom: 3rem; } -.charts { +.printSection { break-inside: avoid; } From b55dd57f0f6435ec87903f2085c459af9043a41a Mon Sep 17 00:00:00 2001 From: Rayce Ramsay Date: Mon, 9 Sep 2024 17:15:27 -0400 Subject: [PATCH 37/37] Minor styling fix for dashboard print --- frontend/styles/Dashboard.module.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/styles/Dashboard.module.css b/frontend/styles/Dashboard.module.css index 6972a67..5a2fe4d 100644 --- a/frontend/styles/Dashboard.module.css +++ b/frontend/styles/Dashboard.module.css @@ -69,6 +69,10 @@ grid-template-columns: repeat(2, minmax(0, 1fr)); } + .printSection:not(:first-child) { + margin-top: 3rem; + } + .wrapper { color: black; }