Skip to content

Commit

Permalink
13 results for managers (#59)
Browse files Browse the repository at this point in the history
* added dynamic manager route

* first code for management function

* fixed bug in results, not showing results of multiple users

* Results for manager page design

* fixed typecheck errors

* Add aggregate count of level of expertise for each role

* update styling
  • Loading branch information
JurreBrandsenInfoSupport authored Mar 29, 2024
1 parent 7e63523 commit 07d262b
Show file tree
Hide file tree
Showing 13 changed files with 831 additions and 37 deletions.
76 changes: 76 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.5",
"@react-pdf/renderer": "^3.4.0",
"@t3-oss/env-nextjs": "^0.9.2",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-table": "^8.15.0",
"@trpc/client": "^10.45.1",
"@trpc/next": "^10.45.1",
"@trpc/react-query": "^10.45.1",
Expand Down
168 changes: 168 additions & 0 deletions src/app/management/[role]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import type { Metadata } from "next";
import { Suspense } from "react";
import { ShowRolesWrapper } from "~/app/result/[role]/page";
import ButtonSkeleton from "~/components/loading/button-loader";
import ShowDataTable from "~/components/show-data-table";
import type { DataByRoleAndQuestion, QuestionResult } from "~/models/types";
import { db } from "~/server/db";

export const metadata: Metadata = {
title: "Management insights",
};

const ManagementPage = async () => {
return (
<div className="container flex flex-col items-center justify-center gap-12 px-4 py-16">
<Suspense fallback={<ButtonSkeleton />}>
<ShowRolesWrapper path="/management" />
</Suspense>
<Suspense fallback={<ButtonSkeleton />}>
<ShowTableWrapper />
</Suspense>
</div>
);
};

const ShowTableWrapper = async () => {
const userAnswersForRole: QuestionResult[] = await db.questionResult.findMany(
{
include: {
question: {
include: {
roles: true,
},
},
},
},
);

// Extract all unique user IDs and answer IDs
const userIds = Array.from(
new Set(userAnswersForRole.map((entry) => entry.userId)),
);
const answerIds = Array.from(
new Set(userAnswersForRole.map((entry) => entry.answerId)),
);

// Fetch all users and answer options in a single query
const [users, answerOptions] = await Promise.all([
db.user.findMany({
where: { id: { in: userIds } },
select: { id: true, name: true, email: true },
}),
db.answerOption.findMany({
where: { id: { in: answerIds } },
select: { id: true, option: true },
}),
]);

// Create a map of user IDs to user objects for easy lookup
const userMap: Record<string, { name: string; email: string }> = {};
for (const user of users) {
userMap[user.id] = {
name: user.name ?? "Unknown User",
email: user.email ?? "Unknown Email",
};
}

// Create a map of answer IDs to answer option objects for easy lookup
const answerOptionMap: Record<string, string> = {};
for (const answerOption of answerOptions) {
answerOptionMap[answerOption.id] =
answerOption.option.toString() ?? "Unknown Answer";
}

// Group the data by roles and questions
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";

if (!dataByRoleAndQuestion[roleName]) {
dataByRoleAndQuestion[roleName] = {};
}
if (!dataByRoleAndQuestion[roleName]![questionText]) {
dataByRoleAndQuestion[roleName]![questionText] = [];
}

dataByRoleAndQuestion[roleName]?.[questionText]?.push({
name: userMap[entry.userId]?.name ?? "Unknown User",
email: userMap[entry.userId]?.email ?? "Unknown Email",
answer: answerOptionMap[entry.answerId] ?? "Unknown Answer",
});

// Sort the answers based on the answer value (0, 1, 2, or 3)
dataByRoleAndQuestion[roleName]?.[questionText]?.sort((a, b) => {
const answerValueA = parseInt(a.answer);
const answerValueB = parseInt(b.answer);
return answerValueA - answerValueB;
});
}
}

const aggregatedDataByRole: Record<
string,
Record<string, { name: string; counts: number[] }>
> = {};

for (const entry of userAnswersForRole) {
for (const role of entry.question.roles ?? []) {
const roleName = role.role || "Unknown Role";

if (!aggregatedDataByRole[roleName]) {
aggregatedDataByRole[roleName] = {};
}

const answerValue = parseInt(answerOptionMap[entry.answerId] ?? "");
const userName = userMap[entry.userId]?.name ?? "Unknown User";
const userEmail = userMap[entry.userId]?.email ?? "Unknown Email";

if (!isNaN(answerValue)) {
if (!aggregatedDataByRole[roleName]?.[userEmail]) {
aggregatedDataByRole[roleName]![userEmail] = {
name: userName,
counts: [0, 0, 0, 0],
};
}
aggregatedDataByRole[roleName]![userEmail]!.counts[answerValue]++;
}
}
}

// 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 (
<ShowDataTable
dataByRoleAndQuestion={dataByRoleAndQuestion}
aggregatedDataByRole={aggregatedDataByRole}
/>
);
};

export default ManagementPage;
52 changes: 16 additions & 36 deletions src/app/result/[role]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import {
type TransformedData,
} from "~/models/types";
import { SelectRoleResults } from "../../../components/select-role-results";
import { slugify } from "~/utils/slugify";
import ResultsWrapper from "~/components/results";

import { type Metadata } from "next";
import ButtonSkeleton from "~/components/loading/button-loader";
import LegendSkeleton from "~/components/loading/results-loader";
import { generateRolesWithHref } from "~/utils/role-utils";

export const metadata: Metadata = {
title: "Results",
Expand All @@ -33,7 +33,7 @@ const Results: React.FC = async () => {
<span className="block sm:inline"> Tech Survey - Results</span>
</h1>
<Suspense fallback={<ButtonSkeleton />}>
<ShowRolesWrapper />
<ShowRolesWrapper path="/result" />
</Suspense>

<Suspense fallback={<LegendSkeleton />}>
Expand All @@ -51,29 +51,8 @@ const Results: React.FC = async () => {
);
};

const ShowRolesWrapper = async () => {
const roles: Role[] = await db.role.findMany();

const availableRoles = roles

// sort roles by general first
.sort((a, b) => {
const roleA = a.role.toLowerCase();
const roleB = b.role.toLowerCase();

if (roleA === "general") return -1;
if (roleB === "general") return 1;

return 0;
})
.map((role) => ({
id: role.id,
href: `/result/${slugify(role.role)}`,
label: role.role,
current: false,
completed: false,
started: false,
}));
export const ShowRolesWrapper = async ({ path }: { path: string }) => {
const availableRoles = await generateRolesWithHref(path)();

return <SelectRoleResults roles={availableRoles} />;
};
Expand All @@ -94,7 +73,7 @@ const ShowResultsWrapper = async () => {

const answerOptions = await db.answerOption.findMany();

let transformedData: TransformedData = {};
const transformedData: TransformedData = {};

userAnswersForRole.forEach((userAnswer) => {
const { question, answerId } = userAnswer;
Expand All @@ -103,21 +82,22 @@ const ShowResultsWrapper = async () => {

roles.forEach((role) => {
const roleName = role?.role ?? "";
if (
roleName &&
transformedData &&
!transformedData[roleName]?.[questionText]
) {
transformedData ??= {};
if (roleName && questionText) {
// Check for existence of roleName and questionText
transformedData[roleName] ??= {};
transformedData![roleName]![questionText] ??= {};
transformedData[roleName]![questionText] ??= {};

const answerString =
answerOptions.find((option) => option.id === answerId)?.option ?? "";
const roleData = transformedData[roleName]?.[questionText] ?? {};
let roleData = transformedData[roleName]?.[questionText];

// Ensure roleData is properly initialized
if (!roleData) {
roleData = {};
transformedData[roleName]![questionText] = roleData;
}

roleData![answerString] = roleData![answerString] ?? 0;
roleData![answerString]++;
roleData[answerString] = (roleData[answerString] ?? 0) + 1;
}
});
});
Expand Down
Loading

0 comments on commit 07d262b

Please sign in to comment.