diff --git a/shared/src/utils/stats/SurveyStatsCalculator.test.ts b/shared/src/utils/stats/SurveyStatsCalculator.test.ts index ce0ca692e..7bd039531 100644 --- a/shared/src/utils/stats/SurveyStatsCalculator.test.ts +++ b/shared/src/utils/stats/SurveyStatsCalculator.test.ts @@ -79,7 +79,12 @@ test('handle edge cases with empty data', async () => { await testEnv.firestore.clearFirestoreData({ projectId }); const emptyCalculator = await SurveyStatsCalculator.build(firestoreAdmin); expect(emptyCalculator.data).toEqual([]); - expect(emptyCalculator.aggregatedData).toEqual({}); + expect(emptyCalculator.aggregatedData).toEqual({ + [SurveyQuestionnaire.Checkin]: {}, + [SurveyQuestionnaire.Onboarding]: {}, + [SurveyQuestionnaire.OffboardedCheckin]: {}, + [SurveyQuestionnaire.Offboarding]: {}, + }); }); const surveyRecords = [ diff --git a/shared/src/utils/stats/SurveyStatsCalculator.ts b/shared/src/utils/stats/SurveyStatsCalculator.ts index f8170b3b3..0f5463168 100644 --- a/shared/src/utils/stats/SurveyStatsCalculator.ts +++ b/shared/src/utils/stats/SurveyStatsCalculator.ts @@ -14,23 +14,33 @@ export interface SurveyAnswersByType { question: Question; } +type AggregatedSurveyData = { [key in SurveyQuestionnaire]: { [questionKey: string]: SurveyAnswersByType } }; + const SUPPORTED_SURVEY_QUESTION_TYPES = [QuestionInputType.RADIO_GROUP, QuestionInputType.CHECKBOX]; export class SurveyStatsCalculator { private readonly _data: SurveyStats[]; - private readonly _aggregatedData: { [key: string]: { [key: string]: SurveyAnswersByType } }; + private readonly _aggregatedData: AggregatedSurveyData; private readonly _oldestDate: Date; - private constructor( - data: SurveyStats[], - aggregatedData: { [key: string]: { [key: string]: SurveyAnswersByType } }, - oldestDate: Date, - ) { + private constructor(data: SurveyStats[], aggregatedData: AggregatedSurveyData, oldestDate: Date) { this._data = data; this._aggregatedData = aggregatedData; this._oldestDate = oldestDate; } + get oldestDate(): Date { + return this._oldestDate; + } + + get data(): SurveyStats[] { + return this._data; + } + + get aggregatedData(): AggregatedSurveyData { + return this._aggregatedData; + } + /** * Builds a new instance of SurveyStatsCalculator * @param firestoreAdmin Firestore admin instance for accessing the database @@ -61,10 +71,15 @@ export class SurveyStatsCalculator { private static aggregateSurveyData(surveysData: Survey[]): { typeCounts: { [type: string]: number }; - aggregatedData: { [key: string]: { [key: string]: SurveyAnswersByType } }; + aggregatedData: AggregatedSurveyData; oldestDate: Date; } { - const aggregatedData: { [key: string]: { [key: string]: SurveyAnswersByType } } = {}; + const aggregatedSurveyData: AggregatedSurveyData = { + [SurveyQuestionnaire.Checkin]: {}, + [SurveyQuestionnaire.Onboarding]: {}, + [SurveyQuestionnaire.OffboardedCheckin]: {}, + [SurveyQuestionnaire.Offboarding]: {}, + }; const typeCounts: { [type: string]: number } = {}; let oldestDate = new Date(); @@ -77,12 +92,12 @@ export class SurveyStatsCalculator { const questionnaire = survey.questionnaire!; typeCounts[questionnaire] = (typeCounts[questionnaire] || 0) + 1; Object.entries(survey.data!).forEach(([questionKey, response]) => { - this.processSurveyResponse(aggregatedData, questionnaire, questionKey, response); + this.processSurveyResponse(aggregatedSurveyData, questionnaire, questionKey, response); }); } }); - return { typeCounts, aggregatedData, oldestDate }; + return { typeCounts, aggregatedData: aggregatedSurveyData, oldestDate }; } private static isCompletedSurvey(survey: Survey): boolean { @@ -90,15 +105,14 @@ export class SurveyStatsCalculator { } private static processSurveyResponse( - aggregatedData: { [key: string]: { [key: string]: SurveyAnswersByType } }, - questionnaire: string, + aggregatedData: { [key in SurveyQuestionnaire]: { [key: string]: SurveyAnswersByType } }, + questionnaire: SurveyQuestionnaire, questionKey: string, response: any, ): void { const question = QUESTIONS_DICTIONARY.get(questionKey); if (!question || !SUPPORTED_SURVEY_QUESTION_TYPES.includes(question.type)) return; - aggregatedData[questionnaire] = aggregatedData[questionnaire] || {}; aggregatedData[questionnaire][questionKey] = aggregatedData[questionnaire][questionKey] || { answers: {}, total: 0, @@ -116,16 +130,4 @@ export class SurveyStatsCalculator { } questionData.total++; } - - get oldestDate(): Date { - return this._oldestDate; - } - - get data(): SurveyStats[] { - return this._data; - } - - get aggregatedData(): { [key: string]: { [key: string]: SurveyAnswersByType } } { - return this._aggregatedData; - } } diff --git a/website/src/app/[lang]/[region]/(website)/survey/responses/barchart-survey-response-component.tsx b/website/src/app/[lang]/[region]/(website)/survey/responses/barchart-survey-response-component.tsx index a42689dea..2adf69c9c 100644 --- a/website/src/app/[lang]/[region]/(website)/survey/responses/barchart-survey-response-component.tsx +++ b/website/src/app/[lang]/[region]/(website)/survey/responses/barchart-survey-response-component.tsx @@ -1,62 +1,61 @@ 'use client'; -import { Component } from 'react'; import { Bar, BarChart, LabelList, ResponsiveContainer, XAxis, YAxis } from 'recharts'; -import './opacity.css'; + export interface ChartData { name: string; value: number; } -export default class BarchartSurveyResponseComponent extends Component<{ data: ChartData[] }> { - render() { - const { data } = this.props; - const barHeight = 30; - const chartHeight = data.length * barHeight + 40; - - let fillColor = 'hsl(var(--foreground))'; +function BarchartSurveyResponseComponent({ data }: { data: ChartData[] }) { + const barHeight = 30; + const chartHeight = data.length * barHeight + 40; - const customLabel = (props: any) => { - const { x, y, width, value } = props; - return ( - - {value} - {value} - - ); - }; + let fillColor = 'hsl(var(--foreground))'; + const customLabel = (props: any) => { + const { x, y, width, value } = props; return ( - - - - - - - `${value}%`} - minTickGap={1} - /> - - + + {value} + {value} + ); - } + }; + + return ( + + + + + + + `${value}%`} + minTickGap={1} + /> + + + ); } + +export default BarchartSurveyResponseComponent; diff --git a/website/src/app/[lang]/[region]/(website)/survey/responses/card-tab.css b/website/src/app/[lang]/[region]/(website)/survey/responses/card-tab.css deleted file mode 100644 index d5fe7ce03..000000000 --- a/website/src/app/[lang]/[region]/(website)/survey/responses/card-tab.css +++ /dev/null @@ -1,3 +0,0 @@ -.card-tab[data-state='inactive'] .card-tab-title { - @apply text-accent-foreground; -} diff --git a/website/src/app/[lang]/[region]/(website)/survey/responses/opacity.css b/website/src/app/[lang]/[region]/(website)/survey/responses/opacity.css deleted file mode 100644 index 8b68bc50d..000000000 --- a/website/src/app/[lang]/[region]/(website)/survey/responses/opacity.css +++ /dev/null @@ -1,3 +0,0 @@ -.survey-chart .recharts-wrapper::before { - @apply to-background pointer-events-none absolute bottom-0 right-0 top-0 w-[40px] bg-gradient-to-r from-transparent content-['']; -} diff --git a/website/src/app/[lang]/[region]/(website)/survey/responses/page.tsx b/website/src/app/[lang]/[region]/(website)/survey/responses/page.tsx index 4f2bd53e2..297ebd21c 100644 --- a/website/src/app/[lang]/[region]/(website)/survey/responses/page.tsx +++ b/website/src/app/[lang]/[region]/(website)/survey/responses/page.tsx @@ -19,16 +19,16 @@ import { Typography, } from '@socialincome/ui'; import { Fragment } from 'react'; -import './card-tab.css'; export const revalidate = 3600; // update once an hour + export default async function Page({ params: { lang } }: DefaultPageProps) { - const surveyStatsCalculator = await SurveyStatsCalculator.build(firestoreAdmin); - const temp = surveyStatsCalculator.data; + const { aggregatedData: data, data: temp, oldestDate } = await SurveyStatsCalculator.build(firestoreAdmin); + const allSurveyData = Object.values(SurveyQuestionnaire) .map((it) => temp.find((survey) => survey.type == it)) .filter((it) => !!it); - const data = surveyStatsCalculator.aggregatedData; + const translator = await Translator.getInstance({ language: lang, namespaces: ['website-responses', 'website-survey'], @@ -57,13 +57,13 @@ export default async function Page({ params: { lang } }: DefaultPageProps) { {allSurveyData.map( (surveyData) => surveyData && ( - - - - {translator.t(surveyData.type + '.title')} + + + + {translator.t(`${surveyData.type}.title`)} - - {translator.t(surveyData.type + '.description')} + + {translator.t(`${surveyData.type}.description`)} {surveyData.total} {translator.t('data-points')} @@ -75,34 +75,34 @@ export default async function Page({ params: { lang } }: DefaultPageProps) { {translator.t('responses-since', { - context: { sinceDate: surveyStatsCalculator.oldestDate.toLocaleDateString() }, + context: { sinceDate: oldestDate.toLocaleDateString() }, })} + {Object.values(SurveyQuestionnaire).map((selectedSurvey) => (
- {Object.keys(data[selectedSurvey] || []).map((key) => ( - - -
- - {translator.t(data[selectedSurvey][key].question.translationKey)} - {data[selectedSurvey][key].question.type == QuestionInputType.CHECKBOX && ( - ({translator.t('multiple-answers')}) + {Object.keys(data[selectedSurvey] || []).map((questionKey) => ( + + +
+ + {translator.t(data[selectedSurvey][questionKey].question.translationKey)} + {data[selectedSurvey][questionKey].question.type == QuestionInputType.CHECKBOX && ( + ({translator.t('multiple-answers')}) )} - - {data[selectedSurvey][key].total} {translator.t('answers')} + {data[selectedSurvey][questionKey].total} {translator.t('answers')}
-
- {data[selectedSurvey][key].answers && ( +
+ {data[selectedSurvey][questionKey].answers && ( + data={convertToBarchartData(data[selectedSurvey][questionKey])} + /> )}
@@ -111,7 +111,7 @@ export default async function Page({ params: { lang } }: DefaultPageProps) { ))} - + ); }