mirror of
https://github.com/CompassConnections/Compass.git
synced 2025-12-23 22:18:43 -05:00
242 lines
7.2 KiB
TypeScript
242 lines
7.2 KiB
TypeScript
import clsx from 'clsx'
|
|
import { Row as rowFor, run } from 'common/supabase/utils'
|
|
import { User } from 'common/user'
|
|
import { useQuestions } from 'web/hooks/use-questions'
|
|
import { useRouter } from 'next/router'
|
|
import { useEffect } from 'react'
|
|
import { Button } from 'web/components/buttons/button'
|
|
import { Col } from 'web/components/layout/col'
|
|
import { Row } from 'web/components/layout/row'
|
|
import { ExpandingInput } from 'web/components/widgets/expanding-input'
|
|
import { Input } from 'web/components/widgets/input'
|
|
import { RadioToggleGroup } from 'web/components/widgets/radio-toggle-group'
|
|
import { Title } from 'web/components/widgets/title'
|
|
import { usePersistentLocalState } from 'web/hooks/use-persistent-local-state'
|
|
import { useUser } from 'web/hooks/use-user'
|
|
import { track } from 'web/lib/service/analytics'
|
|
import { db } from 'web/lib/supabase/db'
|
|
|
|
export type QuestionType = 'multiple_choice' | 'free_response'
|
|
|
|
export const QuestionsForm = (props: { questionType: QuestionType }) => {
|
|
const { questionType } = props
|
|
const questions = useQuestions()
|
|
const user = useUser()
|
|
const router = useRouter()
|
|
return (
|
|
<Col className={'w-full items-center'}>
|
|
<Col
|
|
className={' bg-canvas-0 w-full max-w-2xl justify-between px-6 py-4'}
|
|
>
|
|
<Title>Questions</Title>
|
|
<Col className={'gap-2'}>
|
|
{user &&
|
|
questions
|
|
.filter((q) =>
|
|
questionType !== 'multiple_choice'
|
|
? q.answer_type !== 'multiple_choice'
|
|
: q.answer_type === 'multiple_choice'
|
|
)
|
|
.map((row) => <QuestionRow user={user} key={row.id} row={row} />)}
|
|
</Col>
|
|
<Row>
|
|
<Col className={'mt-2 w-full'}>
|
|
<Row className={'w-full justify-end'}>
|
|
<Button
|
|
color={'indigo-outline'}
|
|
onClick={() => {
|
|
track(`submitquestions page ${questionType}`)
|
|
if (user) router.push(`/${user.username}`)
|
|
else router.push('/')
|
|
}}
|
|
>
|
|
Save
|
|
</Button>
|
|
</Row>
|
|
</Col>
|
|
</Row>
|
|
</Col>
|
|
</Col>
|
|
)
|
|
}
|
|
type compatibilityAnswer = rowFor<'compatibility_answers_free'>
|
|
export type compatibilityAnswerState = Omit<compatibilityAnswer, 'id' | 'created_time'>
|
|
|
|
const fetchPrevious = async (id: number, userId: string) => {
|
|
const res = await run(
|
|
db
|
|
.from('compatibility_answers_free')
|
|
.select('*')
|
|
.eq('question_id', id)
|
|
.eq('creator_id', userId)
|
|
)
|
|
if (res.data.length) {
|
|
return res.data[0]
|
|
}
|
|
return null
|
|
}
|
|
|
|
function getInitialForm(userId: string, id: number) {
|
|
return {
|
|
creator_id: userId,
|
|
free_response: null,
|
|
multiple_choice: null,
|
|
integer: null,
|
|
question_id: id,
|
|
}
|
|
}
|
|
|
|
export const filterKeys = (
|
|
obj: Record<string, any>,
|
|
predicate: (key: string, value: any) => boolean
|
|
): Record<string, any> => {
|
|
const filteredEntries = Object.entries(obj).filter(([key, value]) =>
|
|
predicate(key, value)
|
|
)
|
|
return Object.fromEntries(filteredEntries)
|
|
}
|
|
|
|
const submitAnswer = async (newForm: compatibilityAnswerState) => {
|
|
if (!newForm) return
|
|
const input = {
|
|
...filterKeys(newForm, (key, _) => !['id', 'created_time'].includes(key)),
|
|
} as compatibilityAnswerState
|
|
await run(
|
|
db
|
|
.from('compatibility_answers_free')
|
|
.upsert(input, { onConflict: 'question_id,creator_id' })
|
|
)
|
|
}
|
|
|
|
const QuestionRow = (props: { row: rowFor<'compatibility_prompts'>; user: User }) => {
|
|
const { row, user } = props
|
|
const { question, id, answer_type, multiple_choice_options } = row
|
|
const options = multiple_choice_options as Record<string, number>
|
|
const [form, setForm] = usePersistentLocalState<compatibilityAnswerState>(
|
|
getInitialForm(user.id, id),
|
|
`compatibility_answer_${id}_user_${user.id}`
|
|
)
|
|
|
|
useEffect(() => {
|
|
fetchPrevious(id, user.id).then((res) => {
|
|
if (res) {
|
|
setForm(res)
|
|
}
|
|
})
|
|
}, [row.id])
|
|
|
|
return (
|
|
<Col>
|
|
<span>{question}</span>
|
|
{answer_type === 'free_response' ? (
|
|
<ExpandingInput
|
|
className={'w-full max-w-xl'}
|
|
rows={3}
|
|
value={form.free_response ?? ''}
|
|
onChange={(e) => setForm({ ...form, free_response: e.target.value })}
|
|
onBlur={() => submitAnswer(form)}
|
|
/>
|
|
) : answer_type === 'multiple_choice' && row.multiple_choice_options ? (
|
|
<RadioToggleGroup
|
|
className={'w-44'}
|
|
choicesMap={options}
|
|
setChoice={(choice) => {
|
|
// console.debug(choice)
|
|
const updatedForm = { ...form, multiple_choice: choice }
|
|
setForm(updatedForm)
|
|
submitAnswer(updatedForm)
|
|
}}
|
|
currentChoice={form.multiple_choice ?? -1}
|
|
/>
|
|
) : answer_type === 'integer' ? (
|
|
<Input
|
|
type={'number'}
|
|
className={'w-20'}
|
|
max={1000}
|
|
min={0}
|
|
onChange={(e) =>
|
|
setForm({ ...form, integer: Number(e.target.value) })
|
|
}
|
|
value={form.integer ?? undefined}
|
|
onBlur={() => submitAnswer(form)}
|
|
/>
|
|
) : null}
|
|
</Col>
|
|
)
|
|
}
|
|
|
|
export const IndividualQuestionRow = (props: {
|
|
row: rowFor<'compatibility_prompts'>
|
|
initialAnswer?: rowFor<'compatibility_answers_free'>
|
|
user: User
|
|
onCancel: () => void
|
|
onSubmit?: () => void
|
|
className?: string
|
|
}) => {
|
|
const { row, user, onCancel, onSubmit, initialAnswer, className } = props
|
|
const { id, answer_type, multiple_choice_options } = row
|
|
const options = multiple_choice_options as Record<string, number>
|
|
const [form, setForm] = usePersistentLocalState<compatibilityAnswerState>(
|
|
initialAnswer ?? getInitialForm(user.id, id),
|
|
`compatibility_answer_${id}_user_${user.id}`
|
|
)
|
|
|
|
useEffect(() => {
|
|
fetchPrevious(id, user.id).then((res) => {
|
|
if (res) {
|
|
setForm(res)
|
|
}
|
|
})
|
|
}, [row.id])
|
|
|
|
return (
|
|
<Col className={clsx('gap-4', className)}>
|
|
{answer_type === 'free_response' ? (
|
|
<ExpandingInput
|
|
className={'w-full'}
|
|
rows={3}
|
|
value={form.free_response ?? ''}
|
|
onChange={(e) => setForm({ ...form, free_response: e.target.value })}
|
|
/>
|
|
) : answer_type === 'multiple_choice' && row.multiple_choice_options ? (
|
|
<RadioToggleGroup
|
|
className={'w-44'}
|
|
choicesMap={options}
|
|
setChoice={(choice) => {
|
|
setForm({ ...form, multiple_choice: choice })
|
|
}}
|
|
currentChoice={form.multiple_choice ?? -1}
|
|
/>
|
|
) : answer_type === 'integer' ? (
|
|
<Input
|
|
type={'number'}
|
|
className={'w-20'}
|
|
max={1000}
|
|
min={0}
|
|
onChange={(e) =>
|
|
setForm({ ...form, integer: Number(e.target.value) })
|
|
}
|
|
value={form.integer ?? undefined}
|
|
/>
|
|
) : null}
|
|
<Row className="w-full justify-between">
|
|
<Button color={'gray-outline'} onClick={onCancel}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
color={'indigo'}
|
|
onClick={() => {
|
|
submitAnswer(form).then(() => {
|
|
if (onSubmit) {
|
|
onSubmit()
|
|
}
|
|
})
|
|
}}
|
|
>
|
|
Save
|
|
</Button>
|
|
</Row>
|
|
</Col>
|
|
)
|
|
}
|