diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 289864b..758b819 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -1,6 +1,6 @@
generator client {
provider = "prisma-client-js"
- binaryTargets = ["native", "debian-openssl-3.0.x"]
+ binaryTargets = ["native", "debian-openssl-1.1.x", "debian-openssl-3.0.x"]
}
datasource db {
diff --git a/src/app/find-the-expert/[role]/page.tsx b/src/app/find-the-expert/[role]/page.tsx
index 0905b4a..66ef65d 100644
--- a/src/app/find-the-expert/[role]/page.tsx
+++ b/src/app/find-the-expert/[role]/page.tsx
@@ -1,5 +1,6 @@
import type { Metadata } from "next";
import type { Session } from "next-auth";
+
import { Suspense } from "react";
import { ShowRolesWrapper } from "~/app/result/[role]/page";
import ButtonSkeleton from "~/components/loading/button-loader";
@@ -7,13 +8,9 @@ import { Login } from "~/components/login";
import ShowDataTable from "~/components/show-data-table";
import { getServerAuthSession } from "~/server/auth";
import {
- aggregateDataByRole,
- createUserAndAnswerMaps,
extractUniqueIds,
fetchUserAnswersForRole,
fetchUsersAndAnswerOptions,
- groupDataByRoleAndQuestion,
- sortResults,
} from "~/utils/data-manipulation";
export const metadata: Metadata = {
@@ -63,26 +60,12 @@ const ShowTableWrapper = async () => {
userIds,
answerIds,
);
- const { userMap, answerOptionMap } = createUserAndAnswerMaps(
- users,
- answerOptions,
- );
- const dataByRoleAndQuestion = groupDataByRoleAndQuestion(
- userAnswersForRole,
- userMap,
- answerOptionMap,
- );
- const aggregatedDataByRole = aggregateDataByRole(
- userAnswersForRole,
- userMap,
- answerOptionMap,
- );
- sortResults(aggregatedDataByRole);
return (
);
};
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index ef6894d..b631533 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -16,7 +16,6 @@ import { Button } from "~/components/ui/button";
import { ArrowLeftDarkModeFriendly } from "~/components/svg";
import Link from "next/link";
import { headers } from "next/headers";
-import { Github } from "lucide-react";
import GithubLink from "~/components/github-link";
const inter = Inter({
diff --git a/src/components/show-data-table.tsx b/src/components/show-data-table.tsx
index 2ae611b..340bc3a 100644
--- a/src/components/show-data-table.tsx
+++ b/src/components/show-data-table.tsx
@@ -1,5 +1,5 @@
"use client";
-
+import type { $Enums } from "@prisma/client";
import React from "react";
import { slugify } from "~/utils/slugify";
import {
@@ -10,32 +10,63 @@ import {
import type { ColumnDef } from "@tanstack/react-table";
import { DataTable } from "./data-table";
import { usePathname } from "next/navigation";
+import {
+ aggregateDataByRole,
+ createUserAndAnswerMaps,
+ groupDataByRoleAndQuestion,
+ sortResults,
+} from "~/utils/client-data-manipulation";
+import type { UserAnswersForRoleArray } from "~/models/types";
-type ShowDataTableProps = {
- dataByRoleAndQuestion: Record<
- string,
- Record
- >;
- aggregatedDataByRole: Record<
- string,
- Record<
- string,
- {
- name: string;
- communicationPreferences: string[];
- counts: number[];
- }
- >
- >;
-};
-
-const ShowDataTable: React.FC = ({
- dataByRoleAndQuestion,
- aggregatedDataByRole,
+const ShowDataTable = ({
+ userAnswersForRole,
+ users,
+ answerOptions,
+}: {
+ userAnswersForRole: UserAnswersForRoleArray;
+ users: {
+ name: string | null;
+ id: string;
+ roles: {
+ id: string;
+ role: string;
+ default: boolean;
+ }[];
+ email: string | null;
+ communicationPreferences: {
+ id: string;
+ userId: string;
+ methods: $Enums.CommunicationMethod[];
+ }[];
+ }[];
+ answerOptions: { id: string; option: number }[];
}) => {
const pathname = usePathname();
const currentRole = pathname.split("/").pop();
+
+ const usersForRole: typeof users = users.filter((user) =>
+ user.roles.some((role) => slugify(role.role) === currentRole),
+ );
+
+ const { userMap, answerOptionMap } = createUserAndAnswerMaps(
+ usersForRole,
+ answerOptions,
+ );
+
+ const dataByRoleAndQuestion = groupDataByRoleAndQuestion(
+ userAnswersForRole,
+ userMap,
+ answerOptionMap,
+ );
+
+ const aggregatedDataByRole = aggregateDataByRole(
+ userAnswersForRole,
+ userMap,
+ answerOptionMap,
+ );
+ sortResults(aggregatedDataByRole);
+
return (
<>
{Object.keys(dataByRoleAndQuestion).length === 0 ? (
diff --git a/src/models/types.ts b/src/models/types.ts
index 2d6c310..f7c22be 100644
--- a/src/models/types.ts
+++ b/src/models/types.ts
@@ -126,6 +126,7 @@ export type DataByRoleAndQuestion = Record<
email: string;
communicationPreferences: string[] | undefined;
answer: string;
+ roles: string[];
}[]
>
>;
@@ -136,6 +137,7 @@ export type UserMap = Record<
name: string;
email: string;
communicationPreferences: string[];
+ roles: string[];
}
>;
diff --git a/src/utils/client-data-manipulation.ts b/src/utils/client-data-manipulation.ts
new file mode 100644
index 0000000..06a93d4
--- /dev/null
+++ b/src/utils/client-data-manipulation.ts
@@ -0,0 +1,249 @@
+"use client";
+
+import type { $Enums } from "@prisma/client";
+import type {
+ AggregatedDataByRole,
+ AnswerOptionMap,
+ DataByRoleAndQuestion,
+ Entry,
+ Role,
+ UserAnswersForRoleArray,
+ UserMap,
+} from "~/models/types";
+
+export const createUserAndAnswerMaps = (
+ users: {
+ id: string;
+ name: string | null;
+ email: string | null;
+ communicationPreferences: {
+ id: string;
+ userId: string;
+ methods: $Enums.CommunicationMethod[];
+ }[];
+ roles: { id: string; role: string }[];
+ }[],
+ answerOptions: { id: string; option: number }[],
+) => {
+ const userMap: UserMap = {};
+ for (const user of users) {
+ userMap[user.id] = {
+ name: user.name ?? "Unknown User",
+ email: user.email ?? "Unknown Email",
+ communicationPreferences: user.communicationPreferences.map((method) =>
+ method.methods.toString(),
+ ),
+ roles: user.roles.map((role) => role.role),
+ };
+ }
+
+ const answerOptionMap: AnswerOptionMap = {};
+ for (const answerOption of answerOptions) {
+ answerOptionMap[answerOption.id] =
+ answerOption.option.toString() ?? "Unknown Answer";
+ }
+
+ return { userMap, answerOptionMap };
+};
+
+// Helper function to initialize role and question in the data structure
+const initializeRoleAndQuestion = (
+ dataByRoleAndQuestion: DataByRoleAndQuestion,
+ roleName: string,
+ questionText: string,
+): void => {
+ if (!dataByRoleAndQuestion[roleName]) {
+ dataByRoleAndQuestion[roleName] = {};
+ }
+ if (!dataByRoleAndQuestion[roleName]![questionText]) {
+ dataByRoleAndQuestion[roleName]![questionText] = [];
+ }
+};
+
+// Helper function to push user data into the data structure
+const pushUserData = (
+ dataByRoleAndQuestion: DataByRoleAndQuestion,
+ roleName: string,
+ questionText: string,
+ entry: Entry,
+ userMap: UserMap,
+ answerOptionMap: AnswerOptionMap,
+): void => {
+ // do nothing if there is no user data
+ if (!userMap[entry.userId]) {
+ return;
+ }
+
+ dataByRoleAndQuestion[roleName]![questionText]!.push({
+ name: userMap[entry.userId]?.name ?? "Unknown User",
+ email: userMap[entry.userId]?.email ?? "Unknown Email",
+ communicationPreferences:
+ userMap[entry.userId]!.communicationPreferences?.length > 0
+ ? userMap[entry.userId]?.communicationPreferences
+ : ["Do not contact"],
+ answer: answerOptionMap[entry.answerId] ?? "Unknown Answer",
+ roles: userMap[entry.userId]?.roles ?? [],
+ });
+};
+
+const sortUserData = (
+ dataByRoleAndQuestion: DataByRoleAndQuestion,
+ roleName: string,
+ questionText: string,
+): void => {
+ dataByRoleAndQuestion[roleName]![questionText]!.sort((a, b) => {
+ const answerValueA = parseInt(a.answer);
+ const answerValueB = parseInt(b.answer);
+
+ // We don't want the same person be appear on top of the table all the time. So we shuffle the order of the users with the same answer value.
+ if (answerValueA === answerValueB) {
+ return Math.random() - 0.5;
+ } else {
+ return answerValueA - answerValueB;
+ }
+ });
+};
+
+export const groupDataByRoleAndQuestion = (
+ userAnswersForRole: UserAnswersForRoleArray,
+ userMap: UserMap,
+ answerOptionMap: AnswerOptionMap,
+) => {
+ const dataByRoleAndQuestion: DataByRoleAndQuestion = {};
+
+ for (const entry of userAnswersForRole) {
+ for (const role of entry.question.roles ?? []) {
+ const roleName = role.role || "Unknown Role";
+ const questionText = entry.question.questionText || "Unknown Question";
+
+ initializeRoleAndQuestion(dataByRoleAndQuestion, roleName, questionText);
+ pushUserData(
+ dataByRoleAndQuestion,
+ roleName,
+ questionText,
+ entry,
+ userMap,
+ answerOptionMap,
+ );
+ sortUserData(dataByRoleAndQuestion, roleName, questionText);
+ }
+ }
+
+ return dataByRoleAndQuestion;
+};
+
+const getRoleName = (role: Role) => role.role || "Unknown Role";
+
+const initializeRole = (
+ aggregatedDataByRole: AggregatedDataByRole,
+ roleName: string,
+) => {
+ if (!aggregatedDataByRole[roleName]) {
+ aggregatedDataByRole[roleName] = {};
+ }
+};
+
+const getUserDetails = (userMap: UserMap, entry: Entry) => {
+ const userName = userMap[entry.userId]?.name ?? "Unknown User";
+ const userEmail = userMap[entry.userId]?.email ?? "Unknown Email";
+ let userCommunicationPreferences =
+ userMap[entry.userId]?.communicationPreferences;
+ userCommunicationPreferences =
+ userCommunicationPreferences?.length ?? 0 > 0
+ ? userCommunicationPreferences
+ : ["Do not contact"] ?? [];
+ return { userName, userEmail, userCommunicationPreferences };
+};
+
+const initializeUser = (
+ aggregatedDataByRole: AggregatedDataByRole,
+ roleName: string,
+ userEmail: string,
+ userName: string,
+ userCommunicationPreferences: string[],
+) => {
+ if (!aggregatedDataByRole[roleName]![userEmail]) {
+ aggregatedDataByRole[roleName]![userEmail] = {
+ name: userName,
+ communicationPreferences: userCommunicationPreferences ?? [],
+ counts: [0, 0, 0, 0],
+ };
+ }
+};
+
+const updateCounts = (
+ aggregatedDataByRole: AggregatedDataByRole,
+ roleName: string,
+ userEmail: string,
+ answerValue: number,
+) => {
+ if (!aggregatedDataByRole[roleName]![userEmail]?.counts[answerValue]) {
+ aggregatedDataByRole[roleName]![userEmail]!.counts[answerValue] = 0;
+ }
+ aggregatedDataByRole[roleName]![userEmail]!.counts[answerValue] =
+ (aggregatedDataByRole[roleName]![userEmail]?.counts[answerValue] ?? 0) + 1;
+};
+
+export const aggregateDataByRole = (
+ userAnswersForRole: UserAnswersForRoleArray,
+ userMap: UserMap,
+ answerOptionMap: AnswerOptionMap,
+) => {
+ const aggregatedDataByRole: AggregatedDataByRole = {};
+
+ for (const entry of userAnswersForRole) {
+ for (const role of entry.question.roles ?? []) {
+ const roleName = getRoleName(role);
+ initializeRole(aggregatedDataByRole, roleName);
+
+ if (userMap[entry.userId]) {
+ const answerValue = parseInt(answerOptionMap[entry.answerId] ?? "", 10);
+ const { userName, userEmail, userCommunicationPreferences } =
+ getUserDetails(userMap, entry);
+ initializeUser(
+ aggregatedDataByRole,
+ roleName,
+ userEmail,
+ userName,
+ userCommunicationPreferences!,
+ );
+
+ if (!isNaN(answerValue)) {
+ updateCounts(aggregatedDataByRole, roleName, userEmail, answerValue);
+ }
+ }
+ }
+ }
+
+ return aggregatedDataByRole;
+};
+
+export const sortResults = (aggregatedDataByRole: AggregatedDataByRole) => {
+ // Sorting the results based on the counts of each answer
+ for (const role in aggregatedDataByRole) {
+ for (const user in aggregatedDataByRole[role]) {
+ const counts = aggregatedDataByRole[role]![user]?.counts ?? [0, 0, 0, 0];
+ aggregatedDataByRole[role]![user]!.counts = counts;
+ }
+ }
+
+ // Sorting users based on the total count of 0 answers, then 1, 2, and 3
+ for (const role in aggregatedDataByRole) {
+ const sortedEntries = Object.entries(aggregatedDataByRole[role] ?? {}).sort(
+ (a, b) => {
+ const countsA = a[1].counts;
+ const countsB = b[1].counts;
+ for (let i = 0; i < countsA.length; i++) {
+ const diff = (countsB[i] ?? 0) - (countsA[i] ?? 0);
+ if (diff !== 0) {
+ return diff;
+ }
+ }
+ return 0;
+ },
+ );
+ aggregatedDataByRole[role] = Object.fromEntries(sortedEntries);
+ }
+
+ return aggregatedDataByRole;
+};
diff --git a/src/utils/data-manipulation.ts b/src/utils/data-manipulation.ts
index 163e9fe..51a64ee 100644
--- a/src/utils/data-manipulation.ts
+++ b/src/utils/data-manipulation.ts
@@ -1,14 +1,4 @@
-import type { $Enums } from "@prisma/client";
-import type {
- AggregatedDataByRole,
- AnswerOptionMap,
- DataByRoleAndQuestion,
- Entry,
- Role,
- UserAnswersForRoleArray,
- UserIdAndAnswerId,
- UserMap,
-} from "~/models/types";
+import type { UserIdAndAnswerId } from "~/models/types";
import { db } from "~/server/db";
export const fetchUserAnswersForRole = async () => {
@@ -35,6 +25,7 @@ export const fetchUsersAndAnswerOptions = async (
name: true,
email: true,
communicationPreferences: true,
+ roles: true,
},
}),
db.answerOption.findMany({
@@ -58,232 +49,3 @@ export function extractUniqueIds(userAnswersForRole: UserIdAndAnswerId[]): {
);
return { userIds, answerIds };
}
-
-export const createUserAndAnswerMaps = (
- users: {
- name: string | null;
- email: string | null;
- communicationPreferences: {
- id: string;
- userId: string;
- methods: $Enums.CommunicationMethod[];
- }[];
- id: string;
- }[],
- answerOptions: { id: string; option: number }[],
-) => {
- const userMap: UserMap = {};
- for (const user of users) {
- userMap[user.id] = {
- name: user.name ?? "Unknown User",
- email: user.email ?? "Unknown Email",
- communicationPreferences: user.communicationPreferences.map((method) =>
- method.methods.toString(),
- ),
- };
- }
-
- const answerOptionMap: AnswerOptionMap = {};
- for (const answerOption of answerOptions) {
- answerOptionMap[answerOption.id] =
- answerOption.option.toString() ?? "Unknown Answer";
- }
-
- return { userMap, answerOptionMap };
-};
-
-// Helper function to initialize role and question in the data structure
-const initializeRoleAndQuestion = (
- dataByRoleAndQuestion: DataByRoleAndQuestion,
- roleName: string,
- questionText: string,
-): void => {
- if (!dataByRoleAndQuestion[roleName]) {
- dataByRoleAndQuestion[roleName] = {};
- }
- if (!dataByRoleAndQuestion[roleName]![questionText]) {
- dataByRoleAndQuestion[roleName]![questionText] = [];
- }
-};
-
-// Helper function to push user data into the data structure
-const pushUserData = (
- dataByRoleAndQuestion: DataByRoleAndQuestion,
- roleName: string,
- questionText: string,
- entry: Entry,
- userMap: UserMap,
- answerOptionMap: AnswerOptionMap,
-): void => {
- dataByRoleAndQuestion[roleName]![questionText]!.push({
- name: userMap[entry.userId]?.name ?? "Unknown User",
- email: userMap[entry.userId]?.email ?? "Unknown Email",
- communicationPreferences:
- userMap[entry.userId]!.communicationPreferences?.length > 0
- ? userMap[entry.userId]?.communicationPreferences
- : ["Do not contact"],
- answer: answerOptionMap[entry.answerId] ?? "Unknown Answer",
- });
-};
-
-const sortUserData = (
- dataByRoleAndQuestion: DataByRoleAndQuestion,
- roleName: string,
- questionText: string,
-): void => {
- dataByRoleAndQuestion[roleName]![questionText]!.sort((a, b) => {
- const answerValueA = parseInt(a.answer);
- const answerValueB = parseInt(b.answer);
-
- // We don't want the same person be appear on top of the table all the time. So we shuffle the order of the users with the same answer value.
- if (answerValueA === answerValueB) {
- return Math.random() - 0.5;
- } else {
- return answerValueA - answerValueB;
- }
- });
-};
-
-export const groupDataByRoleAndQuestion = (
- userAnswersForRole: UserAnswersForRoleArray,
- userMap: UserMap,
- answerOptionMap: AnswerOptionMap,
-) => {
- const dataByRoleAndQuestion: DataByRoleAndQuestion = {};
-
- for (const entry of userAnswersForRole) {
- for (const role of entry.question.roles ?? []) {
- const roleName = role.role || "Unknown Role";
- const questionText = entry.question.questionText || "Unknown Question";
-
- initializeRoleAndQuestion(dataByRoleAndQuestion, roleName, questionText);
- pushUserData(
- dataByRoleAndQuestion,
- roleName,
- questionText,
- entry,
- userMap,
- answerOptionMap,
- );
- sortUserData(dataByRoleAndQuestion, roleName, questionText);
- }
- }
-
- return dataByRoleAndQuestion;
-};
-
-const getRoleName = (role: Role) => role.role || "Unknown Role";
-
-const initializeRole = (
- aggregatedDataByRole: AggregatedDataByRole,
- roleName: string,
-) => {
- if (!aggregatedDataByRole[roleName]) {
- aggregatedDataByRole[roleName] = {};
- }
-};
-
-const getUserDetails = (userMap: UserMap, entry: Entry) => {
- const userName = userMap[entry.userId]?.name ?? "Unknown User";
- const userEmail = userMap[entry.userId]?.email ?? "Unknown Email";
- let userCommunicationPreferences =
- userMap[entry.userId]?.communicationPreferences;
- userCommunicationPreferences =
- userCommunicationPreferences?.length ?? 0 > 0
- ? userCommunicationPreferences
- : ["Do not contact"] ?? [];
- return { userName, userEmail, userCommunicationPreferences };
-};
-
-const initializeUser = (
- aggregatedDataByRole: AggregatedDataByRole,
- roleName: string,
- userEmail: string,
- userName: string,
- userCommunicationPreferences: string[],
-) => {
- if (!aggregatedDataByRole[roleName]![userEmail]) {
- aggregatedDataByRole[roleName]![userEmail] = {
- name: userName,
- communicationPreferences: userCommunicationPreferences ?? [],
- counts: [0, 0, 0, 0],
- };
- }
-};
-
-const updateCounts = (
- aggregatedDataByRole: AggregatedDataByRole,
- roleName: string,
- userEmail: string,
- answerValue: number,
-) => {
- if (!aggregatedDataByRole[roleName]![userEmail]?.counts[answerValue]) {
- aggregatedDataByRole[roleName]![userEmail]!.counts[answerValue] = 0;
- }
- aggregatedDataByRole[roleName]![userEmail]!.counts[answerValue] =
- (aggregatedDataByRole[roleName]![userEmail]?.counts[answerValue] ?? 0) + 1;
-};
-
-export const aggregateDataByRole = (
- userAnswersForRole: UserAnswersForRoleArray,
- userMap: UserMap,
- answerOptionMap: AnswerOptionMap,
-) => {
- const aggregatedDataByRole: AggregatedDataByRole = {};
-
- for (const entry of userAnswersForRole) {
- for (const role of entry.question.roles ?? []) {
- const roleName = getRoleName(role);
- initializeRole(aggregatedDataByRole, roleName);
-
- if (userMap[entry.userId]) {
- const answerValue = parseInt(answerOptionMap[entry.answerId] ?? "", 10);
- const { userName, userEmail, userCommunicationPreferences } =
- getUserDetails(userMap, entry);
- initializeUser(
- aggregatedDataByRole,
- roleName,
- userEmail,
- userName,
- userCommunicationPreferences!,
- );
-
- if (!isNaN(answerValue)) {
- updateCounts(aggregatedDataByRole, roleName, userEmail, answerValue);
- }
- }
- }
- }
-
- return aggregatedDataByRole;
-};
-
-export const sortResults = (aggregatedDataByRole: AggregatedDataByRole) => {
- // Sorting the results based on the counts of each answer
- for (const role in aggregatedDataByRole) {
- for (const user in aggregatedDataByRole[role]) {
- const counts = aggregatedDataByRole[role]![user]?.counts ?? [0, 0, 0, 0];
- aggregatedDataByRole[role]![user]!.counts = counts;
- }
- }
-
- // Sorting users based on the total count of 0 answers, then 1, 2, and 3
- for (const role in aggregatedDataByRole) {
- const sortedEntries = Object.entries(aggregatedDataByRole[role] ?? {}).sort(
- (a, b) => {
- const countsA = a[1].counts;
- const countsB = b[1].counts;
- for (let i = 0; i < countsA.length; i++) {
- const diff = (countsB[i] ?? 0) - (countsA[i] ?? 0);
- if (diff !== 0) {
- return diff;
- }
- }
- return 0;
- },
- );
- aggregatedDataByRole[role] = Object.fromEntries(sortedEntries);
- }
-
- return aggregatedDataByRole;
-};