From 5c43e2cf6d04157b4ec9d359c5e00d1d9c1ae62e Mon Sep 17 00:00:00 2001 From: ColePurboo Date: Sun, 25 Aug 2024 13:15:30 -0400 Subject: [PATCH 1/3] added analytics queries --- .../src/graphql/typeDefs/analytics.typedef.ts | 26 ++++++++++++ frontend/graphql/queries/analyticsQueries.ts | 41 ++++++++++++++++++- 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/backend/src/graphql/typeDefs/analytics.typedef.ts b/backend/src/graphql/typeDefs/analytics.typedef.ts index 9e3b0c5..3b8e68c 100644 --- a/backend/src/graphql/typeDefs/analytics.typedef.ts +++ b/backend/src/graphql/typeDefs/analytics.typedef.ts @@ -44,6 +44,32 @@ export const analyticsTypeDefs = gql` logApplyClick(scholarId: Int!, jobId: Int!): ApplyClick } + type ScholarJobClicks { + jobId: Int + clickTime: String + scholarEmail: String + scholarName: String + jobTitle: String + employerName: String + } + + type ScholarApplyClicks { + jobId: Int + clickTime: String + scholarEmail: String + scholarName: String + jobTitle: String + employerName: String + } + + type ScholarEmployerClicks { + employerId: Int + clickTime: String + scholarEmail: String + scholarName: String + employerName: String + } + type ClickCount { count: Int } diff --git a/frontend/graphql/queries/analyticsQueries.ts b/frontend/graphql/queries/analyticsQueries.ts index bca787b..ebf6b10 100644 --- a/frontend/graphql/queries/analyticsQueries.ts +++ b/frontend/graphql/queries/analyticsQueries.ts @@ -233,4 +233,43 @@ export const GET_ALL_CLICK_COUNTS = gql` count } } -`; \ No newline at end of file +`; + +export const GET_JOB_CLICKS_FOR_SCHOLAR = gql` + query GetJobClicksForScholar($scholarId: Int!) { + getJobClicksForScholar(scholarId: $scholarId) { + jobId + clickTime + scholarEmail + scholarName + jobTitle + employerName + } + } +`; + + +export const GET_APPLY_CLICKS_FOR_SCHOLAR = gql` + query GetApplyClicksForScholar($scholarId: Int!) { + getApplyClicksForScholar(scholarId: $scholarId) { + jobId + clickTime + scholarEmail + scholarName + jobTitle + employerName + } + } +`; + +export const GET_EMPLOYER_CLICKS_FOR_SCHOLAR = gql` + query GetEmployerClicksForScholar($scholarId: Int!) { + getEmployerClicksForScholar(scholarId: $scholarId) { + employerId + clickTime + scholarEmail + scholarName + employerName + } + } +`; From 35502bfb9354045a8fab825799776a91a6f37d4e Mon Sep 17 00:00:00 2001 From: ColePurboo Date: Sun, 25 Aug 2024 14:09:39 -0400 Subject: [PATCH 2/3] admin panel styling --- frontend/pages/Admin.tsx | 116 +++++++++++------- frontend/src/components/admin/AddBanner.tsx | 7 +- frontend/src/components/admin/UpdateLogo.tsx | 7 +- .../components/AdminPageButtons.module.css | 11 +- 4 files changed, 83 insertions(+), 58 deletions(-) diff --git a/frontend/pages/Admin.tsx b/frontend/pages/Admin.tsx index 5ce931f..babb739 100644 --- a/frontend/pages/Admin.tsx +++ b/frontend/pages/Admin.tsx @@ -1,11 +1,11 @@ import React from "react"; import Navbar from "../src/components/general/Navbar"; -import AddJobButton from "../src/components/admin/AddJobButton" -import AddEmployerButton from "../src/components/admin/AddEmployerButton" +import AddJobButton from "../src/components/admin/AddJobButton"; +import AddEmployerButton from "../src/components/admin/AddEmployerButton"; import RemoveJobButton from "../src/components/admin/RemoveJobButton"; -import styles from "../styles/components/AdminPageButtons.module.css" +import styles from "../styles/components/AdminPageButtons.module.css"; import ExcelReader from "../src/components/admin/ExcelReader"; -import RemoveEmployerButton from "../src/components/admin/RemoveEmployerButton" +import RemoveEmployerButton from "../src/components/admin/RemoveEmployerButton"; import UpdateLogo from "../src/components/admin/UpdateLogo"; import BarChart from "../src/components/admin/BarChart"; import AddBanner from "../src/components/admin/AddBanner"; @@ -15,54 +15,76 @@ import { Analytics } from "@vercel/analytics/react"; import AnalyticsPage from "./Analytics"; import AnalyticsButton from "../src/components/admin/AnalyticsButton"; + // To ensure unauthenticated people don't access // import getServerProps from "../src/utils/getServerProps"; - export default function Admin() { - return ( -
- -
-
-
-

Single-Edit Actions

-
- - - - - -
-
-
-
-

Batch Actions

-
- - - - - - - - -
-
-
-

Analytics

-
- -
-
- -
- {/*
+ return ( +
+ +
+
+
+

Single-Edit Actions

+
+ + + + + + + + + + +
+
+
+
+

Batch Actions

+
+ + +
+
+
+
+

Analytics

+
+ +
+
+
+ {/*
*/} -
-
- ) +
+
+ ); } -// export { getServerProps }; \ No newline at end of file +// export { getServerProps }; diff --git a/frontend/src/components/admin/AddBanner.tsx b/frontend/src/components/admin/AddBanner.tsx index 03f3082..6e6867b 100644 --- a/frontend/src/components/admin/AddBanner.tsx +++ b/frontend/src/components/admin/AddBanner.tsx @@ -30,13 +30,14 @@ export default function AddBanner() { className={styles.button} onClick={() => setOpened(true)} style={{ - height: "2rem", - borderRadius: "0.25rem", + height: "3rem", + borderRadius: "10px", + marginRight: "1rem", border: "1px solid #ced4da", backgroundColor: "white", - color: "black", fontWeight: "bold", fontSize: "1rem", + color: "#806E53", }} > Add new banner diff --git a/frontend/src/components/admin/UpdateLogo.tsx b/frontend/src/components/admin/UpdateLogo.tsx index f89a6f3..1c46f60 100644 --- a/frontend/src/components/admin/UpdateLogo.tsx +++ b/frontend/src/components/admin/UpdateLogo.tsx @@ -26,13 +26,14 @@ export default function UpdateLogo() { className={styles.button} onClick={() => setOpened(true)} style={{ - height: "2rem", - borderRadius: "0.25rem", + height: "3rem", + borderRadius: "10px", + marginRight: "1rem", border: "1px solid #ced4da", backgroundColor: "white", - color: "black", fontWeight: "bold", fontSize: "1rem", + color: "#806E53", }} > Edit Employer Images diff --git a/frontend/styles/components/AdminPageButtons.module.css b/frontend/styles/components/AdminPageButtons.module.css index 4ff19d5..52620c2 100644 --- a/frontend/styles/components/AdminPageButtons.module.css +++ b/frontend/styles/components/AdminPageButtons.module.css @@ -14,9 +14,10 @@ .adminFunctionButton { color: #806E53; background-color: white; - height: 5rem; + height: 3rem; font-weight: bold; - font-size: 2rem; + margin-right: 1rem; + font-size: 1rem; border-radius: 10px; @@ -39,12 +40,12 @@ } .submitButton { - background-color: #806E53; - color: white; + background-color: white; + color: #806E53; font-weight: bold; margin-top: 1rem; padding: 1rem; - border-radius: 5px; + border-radius: 10px; font-size: 1rem; border: none; width: 100% From 1db34586c2679abdccce32ba4b044ae549573e4d Mon Sep 17 00:00:00 2001 From: ColePurboo Date: Sun, 25 Aug 2024 16:28:32 -0400 Subject: [PATCH 3/3] custom analytics resolver --- .../graphql/resolvers/analytics.resolver.ts | 332 +++++++++++++----- .../src/graphql/typeDefs/analytics.typedef.ts | 9 +- frontend/graphql/queries/analyticsQueries.ts | 21 ++ 3 files changed, 276 insertions(+), 86 deletions(-) diff --git a/backend/src/graphql/resolvers/analytics.resolver.ts b/backend/src/graphql/resolvers/analytics.resolver.ts index 29dc741..381659d 100644 --- a/backend/src/graphql/resolvers/analytics.resolver.ts +++ b/backend/src/graphql/resolvers/analytics.resolver.ts @@ -435,10 +435,14 @@ const analyticsResolver = { client.release(); } }, - getApplyClicksForScholar: async (_: any, { scholarId }: any, { dataSources }: any) => { - const { db } = dataSources; - const client = await establishConnection(db); - const query = ` + getApplyClicksForScholar: async ( + _: any, + { scholarId }: any, + { dataSources }: any + ) => { + const { db } = dataSources; + const client = await establishConnection(db); + const query = ` SELECT apply_clicks.scholar_id AS "scholarId", apply_clicks.job_id AS "jobId", @@ -458,30 +462,34 @@ const analyticsResolver = { WHERE apply_clicks.scholar_id = $1 `; - - try { - const resp = await client.query(query, [scholarId]); - const formattedRows = resp.rows.map((row: any) => ({ - scholarId: row.scholarId, - scholarName: row.scholarName, - scholarEmail: row.scholarEmail, - jobId: row.jobId, - jobTitle: row.jobTitle, - employerName: row.employerName, - clickTime: new Date(row.click_time).toISOString(), - })); - return formattedRows; - } catch (err) { - console.error("Error executing query:", err); - throw new Error("Failed to get apply clicks for scholar"); - } finally { - client.release(); - } - }, - getEmployerClicksForScholar: async (_: any, { scholarId }: any, { dataSources }: any) => { - const { db } = dataSources; - const client = await establishConnection(db); - const query = ` + + try { + const resp = await client.query(query, [scholarId]); + const formattedRows = resp.rows.map((row: any) => ({ + scholarId: row.scholarId, + scholarName: row.scholarName, + scholarEmail: row.scholarEmail, + jobId: row.jobId, + jobTitle: row.jobTitle, + employerName: row.employerName, + clickTime: new Date(row.click_time).toISOString(), + })); + return formattedRows; + } catch (err) { + console.error("Error executing query:", err); + throw new Error("Failed to get apply clicks for scholar"); + } finally { + client.release(); + } + }, + getEmployerClicksForScholar: async ( + _: any, + { scholarId }: any, + { dataSources }: any + ) => { + const { db } = dataSources; + const client = await establishConnection(db); + const query = ` SELECT employer_clicks.employer_id, employer.name AS "employerName", @@ -498,29 +506,33 @@ const analyticsResolver = { WHERE employer_clicks.scholar_id = $1 `; - - try { - const resp = await client.query(query, [scholarId]); - const formattedRows = resp.rows.map((row: any) => ({ - employerId: row.employer_id, - employerName: row.employerName, - clickTime: new Date(row.click_time).toISOString(), - scholarId: row.scholar_id, - scholarName: row.scholarName, - scholarEmail: row.scholarEmail, - })); - return formattedRows; - } catch (err) { - console.error("Error executing query:", err); - throw new Error("Failed to get employer clicks for scholar"); - } finally { - client.release(); - } - }, - getJobClicksForScholar: async (_: any, { scholarId }: any, { dataSources }: any) => { - const { db } = dataSources; - const client = await establishConnection(db); - const query = ` + + try { + const resp = await client.query(query, [scholarId]); + const formattedRows = resp.rows.map((row: any) => ({ + employerId: row.employer_id, + employerName: row.employerName, + clickTime: new Date(row.click_time).toISOString(), + scholarId: row.scholar_id, + scholarName: row.scholarName, + scholarEmail: row.scholarEmail, + })); + return formattedRows; + } catch (err) { + console.error("Error executing query:", err); + throw new Error("Failed to get employer clicks for scholar"); + } finally { + client.release(); + } + }, + getJobClicksForScholar: async ( + _: any, + { scholarId }: any, + { dataSources }: any + ) => { + const { db } = dataSources; + const client = await establishConnection(db); + const query = ` SELECT job.job_id, job.title AS "jobTitle", @@ -540,26 +552,26 @@ const analyticsResolver = { WHERE job_clicks.scholar_id = $1 `; - - try { - const resp = await client.query(query, [scholarId]); - const formattedRows = resp.rows.map((row: any) => ({ - jobId: row.job_id, - jobTitle: row.jobTitle, - employerId: row.employer_id, - employerName: row.employerName, - clickTime: new Date(row.click_time).toISOString(), - scholarName: row.scholarName, - scholarEmail: row.scholarEmail, - })); - return formattedRows; - } catch (err) { - console.error("Error executing query:", err); - throw new Error("Failed to get job clicks for scholar"); - } finally { - client.release(); - } - }, + + try { + const resp = await client.query(query, [scholarId]); + const formattedRows = resp.rows.map((row: any) => ({ + jobId: row.job_id, + jobTitle: row.jobTitle, + employerId: row.employer_id, + employerName: row.employerName, + clickTime: new Date(row.click_time).toISOString(), + scholarName: row.scholarName, + scholarEmail: row.scholarEmail, + })); + return formattedRows; + } catch (err) { + console.error("Error executing query:", err); + throw new Error("Failed to get job clicks for scholar"); + } finally { + client.release(); + } + }, getJobLocationRanking: async (_: any, args: any, { dataSources }: any) => { const { db } = dataSources; @@ -762,7 +774,11 @@ const analyticsResolver = { } }, - getCountJobClicksLastWeek: async ( _: any, args: any, { dataSources }: any) => { + getCountJobClicksLastWeek: async ( + _: any, + args: any, + { dataSources }: any + ) => { const { db } = dataSources; const client = await establishConnection(db); const query = `SELECT COUNT(*) FROM job_clicks WHERE click_time >= NOW() - INTERVAL '1 week'`; @@ -779,7 +795,11 @@ const analyticsResolver = { client.release(); } }, - getCountEmployerClicksLastWeek: async ( _: any, args: any, { dataSources }: any) => { + getCountEmployerClicksLastWeek: async ( + _: any, + args: any, + { dataSources }: any + ) => { const { db } = dataSources; const client = await establishConnection(db); const query = `SELECT COUNT(*) FROM employer_clicks WHERE click_time >= NOW() - INTERVAL '1 week'`; @@ -791,12 +811,18 @@ const analyticsResolver = { return formatted_response; } catch (err) { console.error("Error executing query:", err); - throw new Error("Failed to get count of employer clicks in the last week"); + throw new Error( + "Failed to get count of employer clicks in the last week" + ); } finally { client.release(); } }, - getCountApplyClicksLastWeek: async ( _: any, args: any, { dataSources }: any) => { + getCountApplyClicksLastWeek: async ( + _: any, + args: any, + { dataSources }: any + ) => { const { db } = dataSources; const client = await establishConnection(db); const query = `SELECT COUNT(*) FROM apply_clicks WHERE click_time >= NOW() - INTERVAL '1 week'`; @@ -813,7 +839,11 @@ const analyticsResolver = { client.release(); } }, - getCountJobClicksLastMonth: async ( _: any, args: any, { dataSources }: any) => { + getCountJobClicksLastMonth: async ( + _: any, + args: any, + { dataSources }: any + ) => { const { db } = dataSources; const client = await establishConnection(db); const query = `SELECT COUNT(*) FROM job_clicks WHERE click_time >= NOW() - INTERVAL '1 month'`; @@ -830,7 +860,11 @@ const analyticsResolver = { client.release(); } }, - getCountEmployerClicksLastMonth: async ( _: any, args: any, { dataSources }: any) => { + getCountEmployerClicksLastMonth: async ( + _: any, + args: any, + { dataSources }: any + ) => { const { db } = dataSources; const client = await establishConnection(db); const query = `SELECT COUNT(*) FROM employer_clicks WHERE click_time >= NOW() - INTERVAL '1 month'`; @@ -842,12 +876,18 @@ const analyticsResolver = { return formatted_response; } catch (err) { console.error("Error executing query:", err); - throw new Error("Failed to get count of employer clicks in the last month"); + throw new Error( + "Failed to get count of employer clicks in the last month" + ); } finally { client.release(); } }, - getCountApplyClicksLastMonth: async ( _: any, args: any, { dataSources }: any) => { + getCountApplyClicksLastMonth: async ( + _: any, + args: any, + { dataSources }: any + ) => { const { db } = dataSources; const client = await establishConnection(db); const query = `SELECT COUNT(*) FROM apply_clicks WHERE click_time >= NOW() - INTERVAL '1 month'`; @@ -857,15 +897,20 @@ const analyticsResolver = { count: parseInt(resp.rows[0].count), }; return formatted_response; - } catch (err) { console.error("Error executing query:", err); - throw new Error("Failed to get count of apply clicks in the last month"); + throw new Error( + "Failed to get count of apply clicks in the last month" + ); } finally { client.release(); } }, - getCountJobClicksLastYear: async ( _: any, args: any, { dataSources }: any) => { + getCountJobClicksLastYear: async ( + _: any, + args: any, + { dataSources }: any + ) => { const { db } = dataSources; const client = await establishConnection(db); const query = `SELECT COUNT(*) FROM job_clicks WHERE click_time >= NOW() - INTERVAL '1 year'`; @@ -882,7 +927,11 @@ const analyticsResolver = { client.release(); } }, - getCountEmployerClicksLastYear: async ( _: any, args: any, { dataSources }: any) => { + getCountEmployerClicksLastYear: async ( + _: any, + args: any, + { dataSources }: any + ) => { const { db } = dataSources; const client = await establishConnection(db); const query = `SELECT COUNT(*) FROM employer_clicks WHERE click_time >= NOW() - INTERVAL '1 year'`; @@ -894,12 +943,18 @@ const analyticsResolver = { return formatted_response; } catch (err) { console.error("Error executing query:", err); - throw new Error("Failed to get count of employer clicks in the last year"); + throw new Error( + "Failed to get count of employer clicks in the last year" + ); } finally { client.release(); } }, - getCountApplyClicksLastYear: async ( _: any, args: any, { dataSources }: any) => { + getCountApplyClicksLastYear: async ( + _: any, + args: any, + { dataSources }: any + ) => { const { db } = dataSources; const client = await establishConnection(db); const query = `SELECT COUNT(*) FROM apply_clicks WHERE click_time >= NOW() - INTERVAL '1 year'`; @@ -917,6 +972,113 @@ const analyticsResolver = { } }, + getNumberOfAllowedScholars: async ( + _: any, + args: any, + { dataSources }: any + ) => { + const { db } = dataSources; + const client = await establishConnection(db); + const query = `SELECT COUNT(*) FROM allowedscholars`; + try { + const resp = await client.query(query); + console.log(typeof resp.rows[0].count); + console.log(resp.rows[0].count); + return parseInt(resp.rows[0].count); + } catch (err) { + console.error("Error executing query:", err); + throw new Error("Failed to get number of allowed scholars"); + } finally { + client.release(); + } + }, + getNumberOfActiveScholars: async ( + _: any, + args: any, + { dataSources }: any + ) => { + const { db } = dataSources; + const client = await establishConnection(db); + const query = `SELECT COUNT(*) FROM scholar`; + try { + const resp = await client.query(query); + console.log(typeof resp.rows[0].count); + console.log(resp.rows[0].count); + return parseInt(resp.rows[0].count); + } catch (err) { + console.error("Error executing query:", err); + throw new Error("Failed to get number of active scholars"); + } finally { + client.release(); + } + }, + + getClicksCustomAnalytics: async ( + _: any, + { startDate, endDate, interval, clickType}: any, + { dataSources }: any + ) => { + const { db } = dataSources; + const client = await establishConnection(db); + let dateFormat; + switch (interval) { + case "daily": + dateFormat = "YYYY-MM-DD"; + break; + case "weekly": + dateFormat = "YYYY-MM W"; // Year and week number + break; + case "monthly": + dateFormat = "YYYY-MM"; + break; + case "yearly": + dateFormat = "YYYY"; + break; + default: + throw new Error( + "Invalid interval. Choose from 'daily', 'weekly', 'monthly', or 'yearly'." + ); + } + let tableName; + switch (clickType) { + case "job": + tableName = "job_clicks"; + break; + case "employer": + tableName = "employer_clicks"; + break; + case "apply": + tableName = "apply_clicks"; + break; + default: + throw new Error( + "Invalid click type. Choose from 'job', 'employer', or 'apply'." + ); + } + + const query = `SELECT + TO_CHAR(click_time, $3) as date, + count(*) as count + FROM + ${tableName} + WHERE + click_time BETWEEN $1 AND $2 + GROUP BY + date + ORDER BY + date;`; + try { + const resp = await client.query(query, [startDate, endDate, dateFormat]); + const formattedRows = resp.rows.map((row: any) => ({ + date: row.date, + count: parseInt(row.count), + })); + return formattedRows; + } catch (err) { + console.error("Error executing query:", err); + throw new Error("Failed to get custom analytics"); + } + }, }, Mutation: { diff --git a/backend/src/graphql/typeDefs/analytics.typedef.ts b/backend/src/graphql/typeDefs/analytics.typedef.ts index 3b8e68c..a0ad49d 100644 --- a/backend/src/graphql/typeDefs/analytics.typedef.ts +++ b/backend/src/graphql/typeDefs/analytics.typedef.ts @@ -35,7 +35,9 @@ export const analyticsTypeDefs = gql` getCountJobClicksLastYear: ClickCount getCountApplyClicksLastYear: ClickCount getCountEmployerClicksLastYear: ClickCount - + getNumberOfActiveScholars: Int + getNumberOfAllowedScholars: Int + getClicksCustomAnalytics(startDate: Date, endDate: Date, interval: String, clickType: String): [CustomAnalytics] } type Mutation { @@ -44,6 +46,11 @@ export const analyticsTypeDefs = gql` logApplyClick(scholarId: Int!, jobId: Int!): ApplyClick } + type CustomAnalytics { + date: Date + count: Int + } + type ScholarJobClicks { jobId: Int clickTime: String diff --git a/frontend/graphql/queries/analyticsQueries.ts b/frontend/graphql/queries/analyticsQueries.ts index ebf6b10..c891cb0 100644 --- a/frontend/graphql/queries/analyticsQueries.ts +++ b/frontend/graphql/queries/analyticsQueries.ts @@ -273,3 +273,24 @@ export const GET_EMPLOYER_CLICKS_FOR_SCHOLAR = gql` } } `; + +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 + } + } + `;