Skip to content

Commit

Permalink
[WIP] Trying things
Browse files Browse the repository at this point in the history
  • Loading branch information
Gavriil-Tzortzakis committed Nov 3, 2024
1 parent 671a26e commit 7e5dbb5
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 31 deletions.
20 changes: 20 additions & 0 deletions shared/locales/en/website-responses.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"select-survey": "Select survey",
"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"
}
}
73 changes: 73 additions & 0 deletions shared/src/utils/stats/SurveyStatsCalculator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
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 [question, response] of Object.entries(item.data)) {
if (!ignored.includes(question)) {
aggregatedData[item.questionnaire] = aggregatedData[item.questionnaire] || {};
aggregatedData[item.questionnaire][question] = aggregatedData[item.questionnaire][question] || {
answers: [],
};
aggregatedData[item.questionnaire][question].answers.push(response);
}
}
}
});
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;
}
}
34 changes: 3 additions & 31 deletions ui/src/components/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,25 @@

import * as TabsPrimitive from '@radix-ui/react-tabs';
import * as React from 'react';
import { cn } from '../lib/utils';

const Tabs = TabsPrimitive.Root;

const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
'bg-muted text-muted-foreground inline-flex h-10 items-center justify-center rounded-md p-1',
className,
)}
{...props}
/>
));
>(({ className, ...props }, ref) => <TabsPrimitive.List ref={ref} className={className} {...props} />);
TabsList.displayName = TabsPrimitive.List.displayName;

const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
'ring-offset-background focus-visible:ring-ring data-[state=active]:bg-background data-[state=active]:text-foreground inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm',
className,
)}
{...props}
/>
));
>(({ className, ...props }, ref) => <TabsPrimitive.Trigger ref={ref} className={className} {...props} />);
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;

const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
'ring-offset-background focus-visible:ring-ring mt-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
className,
)}
{...props}
/>
));
>(({ className, ...props }, ref) => <TabsPrimitive.Content ref={ref} className={className} {...props} />);
TabsContent.displayName = TabsPrimitive.Content.displayName;

export { Tabs, TabsContent, TabsList, TabsTrigger };
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { DefaultPageProps } from '@/app/[lang]/[region]';
import { firestoreAdmin } from '@/firebase-admin';
import { SurveyQuestionnaire } from '@socialincome/shared/src/types/survey';
import { Translator } from '@socialincome/shared/src/utils/i18n';
import { SurveyStatsCalculator } from '@socialincome/shared/src/utils/stats/SurveyStatsCalculator';
import {
Badge,
BaseContainer,
Card,
CardTitle,
Table,
TableBody,
TableCell,
TableRow,
Tabs,
TabsContent,
TabsList,
TabsTrigger,
Typography,
} from '@socialincome/ui';

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'],
});

return (
<BaseContainer className="mx-auto flex flex-col">
<Typography size="4xl" className="mt-4" weight="bold">
{translator.t('title')}
</Typography>

<Typography size="xl" className="mx-2 mt-8" weight="bold">
{translator.t('select-survey')}
</Typography>
<Tabs defaultValue={SurveyQuestionnaire.Onboarding}>
<TabsList className="mx-auto mt-2 grid grid-cols-4">
{allSurveyData.map(
(surveyData) =>
surveyData && (
<TabsTrigger key={surveyData.type} value={surveyData.type} className="tabs-trigger" asChild={true}>
<Card className="data-[state=active]:bg-primary ml-1 bg-neutral-50 p-2 data-[state=inactive]:cursor-pointer data-[state=active]:text-white">
<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>
</TabsTrigger>
),
)}
</TabsList>

{Object.values(SurveyQuestionnaire).map((selectedSurvey) => (
<TabsContent value={selectedSurvey} key={selectedSurvey} className={'mt-3.5'}>
<Table key={selectedSurvey}>
<TableBody>
{Object.keys(data[selectedSurvey]).map((key) => (
<TableRow key={selectedSurvey + key + 'statistics'}>
<TableCell key={selectedSurvey + key + 'question'} className="p-1.5 font-medium">
<div className="border-hidden bg-transparent p-2">
<Typography size="2xl" className="text py-2">
{translator.t('survey.questions.' + key.replace('V1', 'TitleV1'))}
</Typography>
<Badge className="bg-amber-400 text-white">
<Typography className="mt-1">{data[selectedSurvey][key].answers.length} answers</Typography>
</Badge>
</div>
</TableCell>
<TableCell key={selectedSurvey + key + 'answers'}>
<Typography className="border-hidden bg-transparent p-2">
{JSON.stringify(data[selectedSurvey][key].answers)}
</Typography>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TabsContent>
))}
</Tabs>
</BaseContainer>
);
}

0 comments on commit 7e5dbb5

Please sign in to comment.