-
-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
671a26e
commit c6ea5d5
Showing
3 changed files
with
157 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"title": "Survey Responses", | ||
"onboarding": { | ||
"title": "Onboarding Survey", | ||
"description": "Filled out once before recipient is joining the program" | ||
}, | ||
"checkin": { | ||
"title": "Check-in Survey", | ||
"description": "Filled out every 6 months while recipient is in the program" | ||
}, | ||
"offboarding": { | ||
"title": "Offboarding Survey", | ||
"description": "Filled out once when recipient is finishing the program" | ||
}, | ||
"offboarded-checkin": { | ||
"title": "Follow-up Survey", | ||
"description": "Filled out every 6 months after recipient left the program" | ||
} | ||
|
||
|
||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import _ from 'lodash'; | ||
import { FirestoreAdmin } from '../../firebase/admin/FirestoreAdmin'; | ||
import { Recipient, RECIPIENT_FIRESTORE_PATH } from '../../types/recipient'; | ||
import { Survey, SURVEY_FIRETORE_PATH, SurveyQuestionnaire, SurveyStatus } from '../../types/survey'; | ||
|
||
export interface SurveyStats { | ||
total: number; | ||
type: SurveyQuestionnaire; | ||
} | ||
|
||
export interface SurveyAnswersByType { | ||
answers: any[]; | ||
} | ||
|
||
export class SurveyStatsCalculator { | ||
private readonly _data: SurveyStats[]; | ||
private readonly _aggregatedData: { [key: string]: { [key: string]: SurveyAnswersByType } }; | ||
|
||
private constructor( | ||
data: SurveyStats[], | ||
aggregatedData: { | ||
[key: string]: { [key: string]: SurveyAnswersByType }; | ||
}, | ||
) { | ||
this._data = data; | ||
this._aggregatedData = aggregatedData; | ||
} | ||
|
||
/** | ||
* @param firestoreAdmin | ||
*/ | ||
static async build(firestoreAdmin: FirestoreAdmin): Promise<SurveyStatsCalculator> { | ||
const recipients = await firestoreAdmin.collection<Recipient>(RECIPIENT_FIRESTORE_PATH).get(); | ||
let documents = await Promise.all( | ||
recipients.docs | ||
.filter((recipient) => !recipient.get('test_recipient')) | ||
.map(async (recipient) => { | ||
return await firestoreAdmin | ||
.collection<Survey>(`${RECIPIENT_FIRESTORE_PATH}/${recipient.id}/${SURVEY_FIRETORE_PATH}`) | ||
.get(); | ||
}), | ||
); | ||
let ignored = ['pageNo']; | ||
let surveysData = documents.flatMap((snapshot) => snapshot.docs).map((survey) => survey.data()); | ||
|
||
let aggregatedData: { [key: string]: { [key: string]: SurveyAnswersByType } } = {}; | ||
const typeCounts: { [type: string]: number } = {}; | ||
surveysData.forEach((item) => { | ||
if (item.status === SurveyStatus.Completed) { | ||
typeCounts[item.questionnaire] = (typeCounts[item.questionnaire] || 0) + 1; | ||
for (const [key, value] of Object.entries(item.data)) { | ||
if (!ignored.includes(key)) { | ||
aggregatedData[item.questionnaire] = aggregatedData[item.questionnaire] || {}; | ||
aggregatedData[item.questionnaire][key] = aggregatedData[item.questionnaire][key] || { answers: [] }; | ||
aggregatedData[item.questionnaire][key].answers.push(value); | ||
} | ||
} | ||
} | ||
}); | ||
const data = Object.entries(typeCounts).map(([type, total]) => ({ type, total }) as SurveyStats); | ||
|
||
return new SurveyStatsCalculator(data, aggregatedData); | ||
} | ||
|
||
get data(): SurveyStats[] { | ||
return this._data; | ||
} | ||
|
||
get aggregatedData(): { [key: string]: { [key: string]: SurveyAnswersByType } } { | ||
return this._aggregatedData; | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
website/src/app/[lang]/[region]/(website)/survey/responses/page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { firestoreAdmin } from '@/firebase-admin'; | ||
import { SurveyStatsCalculator } from '@socialincome/shared/src/utils/stats/SurveyStatsCalculator'; | ||
import { Badge, BaseContainer, Card, CardTitle, Typography } from '@socialincome/ui'; | ||
import { Translator } from '@socialincome/shared/src/utils/i18n'; | ||
import { DefaultPageProps } from '@/app/[lang]/[region]'; | ||
import { SurveyQuestionnaire } from '@socialincome/shared/src/types/survey'; | ||
|
||
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 allSurveyData = Object.values(SurveyQuestionnaire).map((it) => temp.find((survey) => survey.type == it)); | ||
const data = surveyStatsCalculator.aggregatedData; | ||
const translator = await Translator.getInstance({ | ||
language: lang, | ||
namespaces: ['website-responses','website-survey'], | ||
}); | ||
let selectedSurvey = SurveyQuestionnaire.Onboarding; | ||
|
||
return ( | ||
<BaseContainer className="mx-auto flex max-w-3xl flex-col space-y-12"> | ||
<Typography size="4xl" weight="bold"> | ||
{translator.t('title')} | ||
</Typography> | ||
<div className="flex flex-col space-y-2 py-4"> | ||
<div className="mt-2 grid grid-cols-4 gap-2"> | ||
{allSurveyData.map( | ||
(surveyData) => | ||
surveyData && ( | ||
<Card key={surveyData.type} className="p-2 bg-neutral-50"> | ||
<CardTitle className="text py-2">{translator.t(surveyData.type + '.title')}</CardTitle> | ||
|
||
<Typography className="mt-2">{translator.t(surveyData.type + '.description')}</Typography> | ||
<Typography className="mt-3">{surveyData.total} data points</Typography> | ||
</Card> | ||
), | ||
)} | ||
</div> | ||
</div> | ||
<div className="flex flex-col"> | ||
<div className="mt-2 grid grid-cols-1 gap-2"> | ||
{Object.keys(data[selectedSurvey]).map((key) => ( | ||
<Card key={key} className="p-2 bg-transparent"> | ||
<CardTitle> | ||
<div> | ||
<Typography className="text py-2">{translator.t('survey.questions.'+key.replace("V1","TitleV1"))}</Typography> | ||
<Badge className="bg-amber-400"> | ||
<Typography className="mt-1 ">{data[selectedSurvey][key].answers.length} answers</Typography> | ||
</Badge> | ||
</div> | ||
</CardTitle> | ||
<div> | ||
<Typography className="mt-2">{JSON.stringify(data[selectedSurvey][key].answers)}</Typography> | ||
</div> | ||
</Card> | ||
|
||
|
||
))} | ||
</div> | ||
</div> | ||
</BaseContainer> | ||
); | ||
} |