[questions][feat] add similar questions check (#468)

Co-authored-by: wlren <weilinwork99@gmail.com>
pull/470/head
Jeff Sieu 2 years ago committed by GitHub
parent 568b674322
commit 8fdea6b5bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,6 @@
import { Fragment, useState } from 'react';
import { Dialog, Transition } from '@headlessui/react';
import { HorizontalDivider } from '@tih/ui';
import { HorizontalDivider, useToast } from '@tih/ui';
import DiscardDraftDialog from './DiscardDraftDialog';
import type { ContributeQuestionFormProps } from './forms/ContributeQuestionForm';
@ -21,6 +21,8 @@ export default function ContributeQuestionDialog({
}: ContributeQuestionDialogProps) {
const [showDiscardDialog, setShowDiscardDialog] = useState(false);
const { showToast } = useToast();
const handleDraftDiscard = () => {
setShowDiscardDialog(false);
onCancel();
@ -75,6 +77,14 @@ export default function ContributeQuestionDialog({
<div className="mt-2">
<ContributeQuestionForm
onDiscard={() => setShowDiscardDialog(true)}
onSimilarQuestionFound={() => {
onCancel();
showToast({
title:
'Your response has been recorded. Draft discarded.',
variant: 'success',
});
}}
onSubmit={(data) => {
onSubmit(data);
onCancel();

@ -88,10 +88,12 @@ type ReceivedStatisticsProps =
type CreateEncounterProps =
| {
createEncounterButtonText: string;
onReceivedSubmit: (data: CreateQuestionEncounterData) => void;
showCreateEncounterButton: true;
}
| {
createEncounterButtonText?: never;
onReceivedSubmit?: never;
showCreateEncounterButton?: false;
};
@ -132,6 +134,7 @@ export default function BaseQuestionCard({
showAnswerStatistics,
showReceivedStatistics,
showCreateEncounterButton,
createEncounterButtonText,
showActionButton,
actionButtonLabel,
onActionButtonClick,
@ -238,7 +241,7 @@ export default function BaseQuestionCard({
<Button
addonPosition="start"
icon={CheckIcon}
label="I received this too"
label={createEncounterButtonText}
size="sm"
variant="tertiary"
onClick={(event) => {

@ -3,10 +3,10 @@ import BaseQuestionCard from './BaseQuestionCard';
export type SimilarQuestionCardProps = Omit<
BaseQuestionCardProps & {
showActionButton: true;
showAggregateStatistics: false;
showActionButton: false;
showAggregateStatistics: true;
showAnswerStatistics: false;
showCreateEncounterButton: false;
showCreateEncounterButton: true;
showDeleteButton: false;
showHover: true;
showReceivedStatistics: false;
@ -22,26 +22,20 @@ export type SimilarQuestionCardProps = Omit<
| 'showHover'
| 'showReceivedStatistics'
| 'showVoteButtons'
> & {
onSimilarQuestionClick: () => void;
};
>;
export default function SimilarQuestionCard(props: SimilarQuestionCardProps) {
const { onSimilarQuestionClick, ...rest } = props;
return (
<BaseQuestionCard
actionButtonLabel="Yes, this is my question"
showActionButton={true}
showAggregateStatistics={false}
showActionButton={false}
showAggregateStatistics={true}
showAnswerStatistics={false}
showCreateEncounterButton={false}
showCreateEncounterButton={true}
showDeleteButton={false}
showHover={true}
showReceivedStatistics={false}
showVoteButtons={false}
onActionButtonClick={onSimilarQuestionClick}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
{...(rest as any)}
{...props}
/>
);
}

@ -1,15 +1,21 @@
import { startOfMonth } from 'date-fns';
import { useEffect, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { ArrowPathIcon } from '@heroicons/react/20/solid';
import type { QuestionsQuestionType } from '@prisma/client';
import type { TypeaheadOption } from '@tih/ui';
import { CheckboxInput } from '@tih/ui';
import { Button, HorizontalDivider, Select, TextArea } from '@tih/ui';
import { QUESTION_TYPES } from '~/utils/questions/constants';
import relabelQuestionAggregates from '~/utils/questions/relabelQuestionAggregates';
import {
useFormRegister,
useSelectRegister,
} from '~/utils/questions/useFormRegister';
import { trpc } from '~/utils/trpc';
import SimilarQuestionCard from '../card/question/SimilarQuestionCard';
import CompanyTypeahead from '../typeahead/CompanyTypeahead';
import LocationTypeahead from '../typeahead/LocationTypeahead';
import RoleTypeahead from '../typeahead/RoleTypeahead';
@ -30,26 +36,64 @@ export type ContributeQuestionData = {
export type ContributeQuestionFormProps = {
onDiscard: () => void;
onSimilarQuestionFound: () => void;
onSubmit: (data: ContributeQuestionData) => void;
};
export default function ContributeQuestionForm({
onSubmit,
onDiscard,
onSimilarQuestionFound,
onSubmit,
}: ContributeQuestionFormProps) {
const {
control,
register: formRegister,
handleSubmit,
watch,
} = useForm<ContributeQuestionData>({
defaultValues: {
date: startOfMonth(new Date()),
},
});
const [contentToCheck, setContentToCheck] = useState('');
const { data: similarQuestions } = trpc.useQuery(
['questions.questions.getRelatedQuestions', { content: contentToCheck }],
{
keepPreviousData: true,
},
);
const utils = trpc.useContext();
const { mutateAsync: addEncounterAsync } = trpc.useMutation(
'questions.questions.encounters.user.create',
{
onSuccess: () => {
utils.invalidateQueries(
'questions.questions.encounters.getAggregatedEncounters',
);
utils.invalidateQueries('questions.questions.getQuestionById');
},
},
);
const questionContent = watch('questionContent');
const register = useFormRegister(formRegister);
const selectRegister = useSelectRegister(formRegister);
const [checkedSimilar, setCheckedSimilar] = useState<boolean>(false);
const handleCheckSimilarQuestions = (checked: boolean) => {
setCheckedSimilar(checked);
};
useEffect(() => {
if (questionContent !== contentToCheck) {
setCheckedSimilar(false);
}
}, [questionContent, contentToCheck]);
return (
<div className="flex flex-col justify-between gap-4">
<form
@ -149,12 +193,83 @@ export default function ContributeQuestionForm({
<div className="w-full">
<HorizontalDivider />
</div>
<h2
className="text-primary-900 mb-3
text-lg font-semibold
">
Are these questions the same as yours?
</h2>
<Button
addonPosition="start"
disabled={questionContent === contentToCheck}
icon={ArrowPathIcon}
label="Refresh similar questions"
variant="primary"
onClick={() => {
setContentToCheck(questionContent);
}}
/>
<div className="flex flex-col gap-y-2">
{similarQuestions?.map((question) => {
const { companyCounts, countryCounts, roleCounts } =
relabelQuestionAggregates(question.aggregatedQuestionEncounters);
return (
<SimilarQuestionCard
key={question.id}
companies={companyCounts}
content={question.content}
countries={countryCounts}
createEncounterButtonText="Yes, this is my question"
questionId={question.id}
roles={roleCounts}
timestamp={
question.seenAt.toLocaleDateString(undefined, {
month: 'short',
year: 'numeric',
}) ?? null
}
type={question.type}
onReceivedSubmit={async (data) => {
await addEncounterAsync({
cityId: data.cityId,
companyId: data.company,
countryId: data.countryId,
questionId: question.id,
role: data.role,
seenAt: data.seenAt,
stateId: data.stateId,
});
onSimilarQuestionFound();
}}
/>
);
})}
{similarQuestions?.length === 0 && (
<p className="font-semibold text-slate-900">
No similar questions found.
</p>
)}
</div>
<div
className="bg-primary-50 flex w-full justify-end gap-y-2 py-3 shadow-[0_0_0_100vmax_theme(colors.primary.50)]"
className="bg-primary-50 flex w-full justify-between gap-y-2 py-3 shadow-[0_0_0_100vmax_theme(colors.primary.50)]"
style={{
// Hack to make the background bleed outside the container
clipPath: 'inset(0 -100vmax)',
}}>
<div className="my-2 flex items-center sm:my-0">
<CheckboxInput
disabled={questionContent !== contentToCheck}
label={
questionContent !== contentToCheck
? 'I have checked that my question is new (Refresh similar questions to proceed)'
: 'I have checked that my question is new'
}
value={checkedSimilar}
onChange={handleCheckSimilarQuestions}
/>
</div>
<div className="flex gap-x-2">
<button
className="focus:ring-primary-500 inline-flex w-full justify-center rounded-md border border-slate-300 bg-white px-4 py-2 text-base font-medium text-slate-700 shadow-sm hover:bg-slate-50 focus:outline-none focus:ring-2 focus:ring-offset-2 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"

@ -193,6 +193,7 @@ export default function QuestionPage() {
{...question}
companies={relabeledAggregatedEncounters?.companyCounts ?? {}}
countries={relabeledAggregatedEncounters?.countryCounts ?? {}}
createEncounterButtonText="I received this too"
questionId={question.id}
receivedCount={undefined}
roles={relabeledAggregatedEncounters?.roleCounts ?? {}}

@ -1,8 +1,6 @@
import { z } from 'zod';
import { TRPCError } from '@trpc/server';
import { JobTitleLabels } from '~/components/shared/JobTitles';
import { createProtectedRouter } from '../context';
import { SortOrder } from '~/types/questions.d';
@ -14,7 +12,7 @@ export const questionsQuestionEncounterUserRouter = createProtectedRouter()
companyId: z.string(),
countryId: z.string(),
questionId: z.string(),
role: z.nativeEnum(JobTitleLabels),
role: z.string(),
seenAt: z.date(),
stateId: z.string().nullish(),
}),

6907
tatus

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save