Skip to content

Commit

Permalink
Add survey support (#21)
Browse files Browse the repository at this point in the history
* feat(@das-dui/api-client): Add Survey Support

* feat(das-dui-tool): Add Survey Support and organising stuff regarding to the news-post component
  • Loading branch information
Florian325 committed Mar 29, 2024
1 parent 71389a2 commit 73ccdce
Show file tree
Hide file tree
Showing 12 changed files with 261 additions and 50 deletions.
6 changes: 4 additions & 2 deletions apps/das-dui-tool/app/(auth)/lesson-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ const DigitalClassbookRoot: FC<DigitalClassbookRootProps> = ({ lesson }) => {
const { data, isLoading } = useQuery({
queryKey: ["classbook-entry", { lessonId: lesson.id }],
queryFn: async () =>
client.getClassbookEntry({ lessonId: lesson.id, date: date }),
client
.getClassbookEntry({ lessonId: lesson.id, date: date })
.then((response) => response.data),
})

return (
Expand All @@ -84,7 +86,7 @@ const DigitalClassbookRoot: FC<DigitalClassbookRootProps> = ({ lesson }) => {
{isLoading ? (
<Spinner />
) : data ? (
<DigitalClassbookSection classbookEntry={data} />
<DigitalClassbookSection classbookEntry={data.data} />
) : (
<Text>
There is no digital classbook entry for this lesson.
Expand Down
2 changes: 2 additions & 0 deletions apps/das-dui-tool/components/news/NewsListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import NewsPostContainer from "./news-post/NewsPostContainer"
import NewsPostHeader from "./news-post/NewsPostHeader"
import NewsPostBody from "./news-post/NewsPostBody"
import NewsPostAttachments from "./news-post/NewsPostAttachments"
import NewsPostSurvey from "./news-post/NewsPostSurvey"

interface NewsListItemProps {
item: NewsResponse.News
Expand All @@ -15,6 +16,7 @@ const NewsListItem: FC<NewsListItemProps> = ({ item }) => {
<NewsPostHeader title={item.title} preview={item.preview} />
<NewsPostBody content_rendered={item.content_rendered} />
<NewsPostAttachments attachments={item.attachments} />
<NewsPostSurvey survey={item.survey} />
</NewsPostContainer>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const NewsPostAttachments: FC<NewsPostAttachmentsProps> = ({ attachments }) => {
return (
<>
{attachments.length > 0 && (
<Card.Footer style={{ padding: 20 }}>
<Card.Footer px="$4" py="$3">
<YGroup
bordered
width={"100%"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type NewsPostBodyProps = Pick<NewsResponse.News, "content_rendered">

const NewsPostBody: FC<NewsPostBodyProps> = ({ content_rendered }) => {
return (
<View style={{ paddingHorizontal: 20 }}>
<View px="$4">
<NewsPostContent content={content_rendered} />
</View>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const NewsPostHeader: FC<NewsPostHeaderProps> = ({ title, preview }) => {
<>
{(title || preview) && (
<Card.Header>
{title && <H4 lineHeight={"$3"}>{title}</H4>}
{title && <H4>{title}</H4>}
{preview && (
<Image
source={{
Expand Down
156 changes: 156 additions & 0 deletions apps/das-dui-tool/components/news/news-post/NewsPostSurvey.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { FC, useEffect, useState } from "react"
import {
Button,
H5,
H6,
ListItem,
Separator,
Spinner,
Text,
View,
XStack,
YStack,
useTheme,
} from "tamagui"
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { FontAwesome } from "@expo/vector-icons"

import GenericIcon from "@/components/ui/GenericIcon"
import useApiClient from "@/hooks/useApiClient"
import { SurveyResponse, SurveyVoteRequest } from "@das-dui/api-client"

interface NewsPostSurveyProps {
survey?: SurveyResponse.Survey
}

const NewsPostSurvey: FC<NewsPostSurveyProps> = ({ survey }) => {
return <>{survey && <SurveySection survey={survey} />}</>
}

interface SurveySectionProps {
survey: SurveyResponse.Survey
}
const SurveySection: FC<SurveySectionProps> = ({ survey }) => {
const theme = useTheme()
const queryClient = useQueryClient()
const client = useApiClient()

useEffect(() => {
queryClient.setQueryData<SurveyResponse.Survey>(
["survey", survey.uuid],
survey
)
}, [])

const { data, isFetching } = useQuery({
queryKey: ["survey", survey.uuid],
queryFn: async () =>
await client
.getSurveyById({ surveyId: survey.uuid })
.then((res) => res.data.data),
})
const { mutate, isPending, isError } = useMutation({
mutationFn: async (vote: SurveyVoteRequest.Vote) => {
return await client.postSurveyVote({ surveyId: survey.uuid }, vote)
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["survey", survey.uuid] })
},
})

const [selectedOptions, setSelectedOptions] = useState<
SurveyVoteRequest.Vote["options"]
>(
data?.meta.options
.filter((option) => option.is_chosen)
.map((option) => option.uuid) ?? []
)

const onOptionPress = (uuid: string) => {
if (data?.can.vote === 1 && !data?.meta.is_user_voted)
if (data?.is_multi_answerable) {
if (selectedOptions.includes(uuid)) {
setSelectedOptions(
selectedOptions.filter((uuid) => uuid !== uuid)
)
} else {
setSelectedOptions([...selectedOptions, uuid])
}
} else {
setSelectedOptions([uuid])
}
}

return (
<View>
<Separator />
<YStack p="$4" gap="$2">
<XStack gap="$2" alignItems="center">
<GenericIcon name="commenting" size={30} />
<H5>Survey</H5>
<Text fontStyle="italic">
(
{data?.is_multi_answerable
? "Multiple Answer"
: "Single Answer"}
)
</Text>
</XStack>
<H6>{data?.question}</H6>
<YStack gap="$1">
{data?.meta.options.map((option) => (
<ListItem
key={option.uuid}
title={option.name}
onPress={() => {
onOptionPress(option.uuid)
}}
hoverTheme
bordered
pressTheme
borderRadius="$4"
{...(selectedOptions.includes(option.uuid) && {
iconAfter: (
<FontAwesome
name="check-circle"
size={20}
color={theme.green10.val}
/>
),
})}
></ListItem>
))}
</YStack>
{data?.can.vote === 1 &&
!data.meta.is_over &&
!data?.meta.is_user_voted && (
<Button
onPress={() => mutate({ options: selectedOptions })}
disabled={isFetching || isPending}
theme={"green"}
>
{isFetching || isPending ? (
<Spinner />
) : (
"Submit Vote"
)}
</Button>
)}
<YStack gap="$2">
{isError && <Text theme={"red_alt1"}>Submit Error</Text>}
{data?.can.vote === 0 && !data.meta.is_over && (
<Text theme={"red_alt1"}>No permission to vote</Text>
)}
{data?.meta.is_over && (
<Text theme={"red_alt1"}>Survey is over</Text>
)}
{data?.meta.is_user_voted && (
<Text theme={"green_alt1"}>Voted!</Text>
)}
</YStack>
</YStack>
</View>
)
}

export default NewsPostSurvey
15 changes: 15 additions & 0 deletions apps/das-dui-tool/components/ui/GenericIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { FontAwesome } from "@expo/vector-icons"
import { FC } from "react"
import { useColorScheme } from "react-native"

interface GenericIconProps
extends Omit<React.ComponentProps<typeof FontAwesome>, "color"> {}

const GenericIcon: FC<GenericIconProps> = ({ size = 28, ...args }) => {
const colorScheme = useColorScheme()
const color = colorScheme === "dark" ? "white" : "black"

return <FontAwesome size={size} color={color} {...args} />
}

export default GenericIcon
31 changes: 29 additions & 2 deletions packages/api-client/src/client/AxiosApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
LeadsResponse,
LoginResponse,
NewsResponse,
SurveyResponse,
SurveyVoteRequest,
TimetableResponse,
TimetableTimeResponse,
UserActivitySummaryResponse,
Expand Down Expand Up @@ -113,6 +115,13 @@ class AxiosApiClient {
return response
}

public getNewsById = async ({ newsId }: { newsId: number }) => {
const response = await this.instance.get<
BaseResponse<NewsResponse.News>
>(`channels/news/${newsId}`)
return response
}

private _getTimes = async () =>
await this.instance.get<
BaseResponse<TimetableTimeResponse.TimetableTime[]>
Expand Down Expand Up @@ -184,14 +193,32 @@ class AxiosApiClient {
const dateValue =
date instanceof Date ? date.toISOString().substring(0, 10) : date

const resp = await this.instance.get<
const response = await this.instance.get<
BaseResponse<ClassbookEntryResponse.ClassbookEntry>
>(`timetables/lessons/${lessonId}/classbook-entry`, {
params: {
date: dateValue,
},
})
return resp.data.data
return response
}

public getSurveyById = async ({ surveyId }: { surveyId: string }) => {
const response = await this.instance.get<
BaseResponse<SurveyResponse.Survey>
>(`channels/surveys/${surveyId}`)
return response
}

public postSurveyVote = async (
{ surveyId }: { surveyId: string },
vote: SurveyVoteRequest.Vote
) => {
const response = await this.instance.put<
BaseResponse<SurveyResponse.Survey>,
SurveyVoteRequest.Vote
>(`channels/surveys/${surveyId}/vote`, vote)
return response
}
}

Expand Down
2 changes: 2 additions & 0 deletions packages/api-client/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ export * as TimetableResponse from "./timetable"
export * as UserActivitySummaryResponse from "./user_activity_summary"
export * as UserResponse from "./user"
export * as ClassbookEntryResponse from "./classbook_entry"
export * as SurveyResponse from "./survey"
export * as SurveyVoteRequest from "./survey_post"
45 changes: 2 additions & 43 deletions packages/api-client/src/types/news.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Survey } from "./survey"

interface User {
id: number
school_id: number
Expand Down Expand Up @@ -172,48 +174,6 @@ interface ChannelPivot {
channel_id: number
}

interface Survey {
can: {
view: number
vote: number
revoke: number
results: number
download: number
end: number
delete: number
}
meta: {
is_over: boolean
options: {
uuid: string
name: string
is_chosen: boolean
}[]
is_user_voted: boolean
csv: string
xls: string
languages: string[]
}
question: string
id: number
uuid: string
is_multi_answerable: boolean
is_anonymous: boolean
is_freetext: boolean
results_visibility: string
has_translations: boolean
created_at: string
updated_at: string
expires_at?: string | null
ended_at?: string | null
deleted_at?: string | null
user: {
id: number
school_id: number
type: string
}
}

interface NewsMeta {
userMeta: Meta
}
Expand Down Expand Up @@ -252,5 +212,4 @@ export type {
Meta,
Can,
ChannelPivot,
Survey,
}
Loading

0 comments on commit 73ccdce

Please sign in to comment.