[questions][feat] add similar questions ui

pull/468/head
Jeff Sieu 3 years ago
parent d2a8ede590
commit c52f24ecc9

@ -48,6 +48,20 @@ type AnswerStatisticsProps =
showAnswerStatistics?: false; showAnswerStatistics?: false;
}; };
type AggregateStatisticsProps =
| {
companies: Record<string, number>;
locations: Record<string, number>;
roles: Record<string, number>;
showAggregateStatistics: true;
}
| {
companies?: never;
locations?: never;
roles?: never;
showAggregateStatistics?: false;
};
type ActionButtonProps = type ActionButtonProps =
| { | {
actionButtonLabel: string; actionButtonLabel: string;
@ -90,16 +104,14 @@ type AddToListProps =
export type BaseQuestionCardProps = ActionButtonProps & export type BaseQuestionCardProps = ActionButtonProps &
AddToListProps & AddToListProps &
AggregateStatisticsProps &
AnswerStatisticsProps & AnswerStatisticsProps &
CreateEncounterProps & CreateEncounterProps &
DeleteProps & DeleteProps &
ReceivedStatisticsProps & ReceivedStatisticsProps &
UpvoteProps & { UpvoteProps & {
companies: Record<string, number>;
content: string; content: string;
locations: Record<string, number>;
questionId: string; questionId: string;
roles: Record<string, number>;
showHover?: boolean; showHover?: boolean;
timestamp: string; timestamp: string;
truncateContent?: boolean; truncateContent?: boolean;
@ -114,6 +126,7 @@ export default function BaseQuestionCard({
receivedCount, receivedCount,
type, type,
showVoteButtons, showVoteButtons,
showAggregateStatistics,
showAnswerStatistics, showAnswerStatistics,
showReceivedStatistics, showReceivedStatistics,
showCreateEncounterButton, showCreateEncounterButton,
@ -145,12 +158,22 @@ export default function BaseQuestionCard({
/> />
)} )}
<div className="flex flex-col items-start gap-2"> <div className="flex flex-col items-start gap-2">
<div className="flex items-baseline justify-between"> <div className="flex items-baseline justify-between self-stretch">
<div className="flex items-center gap-2 text-slate-500"> <div className="flex items-center gap-2 text-slate-500">
<QuestionTypeBadge type={type} /> {showAggregateStatistics && (
<QuestionAggregateBadge statistics={companies} variant="primary" /> <>
<QuestionAggregateBadge statistics={locations} variant="success" /> <QuestionTypeBadge type={type} />
<QuestionAggregateBadge statistics={roles} variant="danger" /> <QuestionAggregateBadge
statistics={companies}
variant="primary"
/>
<QuestionAggregateBadge
statistics={locations}
variant="success"
/>
<QuestionAggregateBadge statistics={roles} variant="danger" />
</>
)}
<p className="text-xs">{timestamp}</p> <p className="text-xs">{timestamp}</p>
{showAddToList && ( {showAddToList && (
<div className="pl-4"> <div className="pl-4">
@ -162,7 +185,7 @@ export default function BaseQuestionCard({
<Button <Button
label={actionButtonLabel} label={actionButtonLabel}
size="sm" size="sm"
variant="tertiary" variant="secondary"
onClick={onActionButtonClick} onClick={onActionButtonClick}
/> />
)} )}

@ -5,6 +5,7 @@ export type QuestionOverviewCardProps = Omit<
BaseQuestionCardProps & { BaseQuestionCardProps & {
showActionButton: false; showActionButton: false;
showAddToList: true; showAddToList: true;
showAggregateStatistics: true;
showAnswerStatistics: false; showAnswerStatistics: false;
showCreateEncounterButton: true; showCreateEncounterButton: true;
showDeleteButton: false; showDeleteButton: false;
@ -15,6 +16,7 @@ export type QuestionOverviewCardProps = Omit<
| 'onActionButtonClick' | 'onActionButtonClick'
| 'showActionButton' | 'showActionButton'
| 'showAddToList' | 'showAddToList'
| 'showAggregateStatistics'
| 'showAnswerStatistics' | 'showAnswerStatistics'
| 'showCreateEncounterButton' | 'showCreateEncounterButton'
| 'showDeleteButton' | 'showDeleteButton'
@ -28,6 +30,7 @@ export default function FullQuestionCard(props: QuestionOverviewCardProps) {
{...props} {...props}
showActionButton={false} showActionButton={false}
showAddToList={true} showAddToList={true}
showAggregateStatistics={true}
showAnswerStatistics={false} showAnswerStatistics={false}
showCreateEncounterButton={true} showCreateEncounterButton={true}
showReceivedStatistics={false} showReceivedStatistics={false}

@ -6,6 +6,7 @@ import BaseQuestionCard from './BaseQuestionCard';
export type QuestionListCardProps = Omit< export type QuestionListCardProps = Omit<
BaseQuestionCardProps & { BaseQuestionCardProps & {
showActionButton: false; showActionButton: false;
showAggregateStatistics: true;
showAnswerStatistics: false; showAnswerStatistics: false;
showDeleteButton: true; showDeleteButton: true;
showVoteButtons: false; showVoteButtons: false;
@ -13,6 +14,7 @@ export type QuestionListCardProps = Omit<
| 'actionButtonLabel' | 'actionButtonLabel'
| 'onActionButtonClick' | 'onActionButtonClick'
| 'showActionButton' | 'showActionButton'
| 'showAggregateStatistics'
| 'showAnswerStatistics' | 'showAnswerStatistics'
| 'showDeleteButton' | 'showDeleteButton'
| 'showVoteButtons' | 'showVoteButtons'
@ -24,6 +26,7 @@ function QuestionListCardWithoutHref(props: QuestionListCardProps) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
{...(props as any)} {...(props as any)}
showActionButton={false} showActionButton={false}
showAggregateStatistics={true}
showAnswerStatistics={false} showAnswerStatistics={false}
showDeleteButton={true} showDeleteButton={true}
showHover={true} showHover={true}

@ -6,6 +6,7 @@ import BaseQuestionCard from './BaseQuestionCard';
export type QuestionOverviewCardProps = Omit< export type QuestionOverviewCardProps = Omit<
BaseQuestionCardProps & { BaseQuestionCardProps & {
showActionButton: false; showActionButton: false;
showAggregateStatistics: true;
showAnswerStatistics: true; showAnswerStatistics: true;
showCreateEncounterButton: false; showCreateEncounterButton: false;
showDeleteButton: false; showDeleteButton: false;
@ -16,6 +17,7 @@ export type QuestionOverviewCardProps = Omit<
| 'onActionButtonClick' | 'onActionButtonClick'
| 'onDelete' | 'onDelete'
| 'showActionButton' | 'showActionButton'
| 'showAggregateStatistics'
| 'showAnswerStatistics' | 'showAnswerStatistics'
| 'showCreateEncounterButton' | 'showCreateEncounterButton'
| 'showDeleteButton' | 'showDeleteButton'
@ -28,6 +30,7 @@ function QuestionOverviewCardWithoutHref(props: QuestionOverviewCardProps) {
<BaseQuestionCard <BaseQuestionCard
{...props} {...props}
showActionButton={false} showActionButton={false}
showAggregateStatistics={true}
showAnswerStatistics={true} showAnswerStatistics={true}
showCreateEncounterButton={false} showCreateEncounterButton={false}
showDeleteButton={false} showDeleteButton={false}

@ -4,7 +4,8 @@ import BaseQuestionCard from './BaseQuestionCard';
export type SimilarQuestionCardProps = Omit< export type SimilarQuestionCardProps = Omit<
BaseQuestionCardProps & { BaseQuestionCardProps & {
showActionButton: true; showActionButton: true;
showAnswerStatistics: true; showAggregateStatistics: false;
showAnswerStatistics: false;
showCreateEncounterButton: false; showCreateEncounterButton: false;
showDeleteButton: false; showDeleteButton: false;
showHover: true; showHover: true;
@ -14,6 +15,7 @@ export type SimilarQuestionCardProps = Omit<
| 'actionButtonLabel' | 'actionButtonLabel'
| 'onActionButtonClick' | 'onActionButtonClick'
| 'showActionButton' | 'showActionButton'
| 'showAggregateStatistics'
| 'showAnswerStatistics' | 'showAnswerStatistics'
| 'showCreateEncounterButton' | 'showCreateEncounterButton'
| 'showDeleteButton' | 'showDeleteButton'
@ -30,12 +32,13 @@ export default function SimilarQuestionCard(props: SimilarQuestionCardProps) {
<BaseQuestionCard <BaseQuestionCard
actionButtonLabel="Yes, this is my question" actionButtonLabel="Yes, this is my question"
showActionButton={true} showActionButton={true}
showAnswerStatistics={true} showAggregateStatistics={false}
showAnswerStatistics={false}
showCreateEncounterButton={false} showCreateEncounterButton={false}
showDeleteButton={false} showDeleteButton={false}
showHover={true} showHover={true}
showReceivedStatistics={true} showReceivedStatistics={false}
showVoteButtons={true} showVoteButtons={false}
onActionButtonClick={onSimilarQuestionClick} onActionButtonClick={onSimilarQuestionClick}
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
{...(rest as any)} {...(rest as any)}

@ -1,6 +1,7 @@
import { startOfMonth } from 'date-fns'; import { startOfMonth } from 'date-fns';
import { useState } from 'react'; import { useEffect, useState } from 'react';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { ArrowPathIcon } from '@heroicons/react/20/solid';
import type { QuestionsQuestionType } from '@prisma/client'; import type { QuestionsQuestionType } from '@prisma/client';
import { import {
Button, Button,
@ -15,7 +16,9 @@ import {
useFormRegister, useFormRegister,
useSelectRegister, useSelectRegister,
} from '~/utils/questions/useFormRegister'; } from '~/utils/questions/useFormRegister';
import { trpc } from '~/utils/trpc';
import SimilarQuestionCard from '../card/question/SimilarQuestionCard';
import CompanyTypeahead from '../typeahead/CompanyTypeahead'; import CompanyTypeahead from '../typeahead/CompanyTypeahead';
import LocationTypeahead from '../typeahead/LocationTypeahead'; import LocationTypeahead from '../typeahead/LocationTypeahead';
import RoleTypeahead from '../typeahead/RoleTypeahead'; import RoleTypeahead from '../typeahead/RoleTypeahead';
@ -45,18 +48,39 @@ export default function ContributeQuestionForm({
control, control,
register: formRegister, register: formRegister,
handleSubmit, handleSubmit,
watch,
} = useForm<ContributeQuestionData>({ } = useForm<ContributeQuestionData>({
defaultValues: { defaultValues: {
date: startOfMonth(new Date()), date: startOfMonth(new Date()),
}, },
}); });
const [contentToCheck, setContentToCheck] = useState('');
const { data: similarQuestions } = trpc.useQuery(
[
'questions.questions.getRelatedQuestionsByContent',
{ content: contentToCheck },
],
{
keepPreviousData: true,
},
);
const questionContent = watch('questionContent');
const register = useFormRegister(formRegister); const register = useFormRegister(formRegister);
const selectRegister = useSelectRegister(formRegister); const selectRegister = useSelectRegister(formRegister);
const [canSubmit, setCanSubmit] = useState<boolean>(false); const [checkedSimilar, setCheckedSimilar] = useState<boolean>(false);
const handleCheckSimilarQuestions = (checked: boolean) => { const handleCheckSimilarQuestions = (checked: boolean) => {
setCanSubmit(checked); setCheckedSimilar(checked);
}; };
useEffect(() => {
if (questionContent !== contentToCheck) {
setCheckedSimilar(false);
}
}, [questionContent, contentToCheck]);
return ( return (
<div className="flex flex-col justify-between gap-4"> <div className="flex flex-col justify-between gap-4">
<form <form
@ -153,35 +177,55 @@ export default function ContributeQuestionForm({
/> />
</div> </div>
</div> </div>
{/* <div className="w-full"> <div className="w-full">
<HorizontalDivider /> <HorizontalDivider />
</div> </div>
<h1 className="mb-3"> <h2
Are these questions the same as yours? TODO:Change to list className="text-primary-900 mb-3
</h1> text-lg font-semibold
<div> ">
<SimilarQuestionCard Are these questions the same as yours?
content="Given an array of integers nums and an integer target, return indices of the two numbers such that they add up. Given an array of integers nums and an integer target, return indices" </h2>
location="Menlo Park, CA" <Button
receivedCount={0} addonPosition="start"
role="Senior Engineering Manager" disabled={questionContent === contentToCheck}
timestamp="Today" icon={ArrowPathIcon}
onSimilarQuestionClick={() => { label="Refresh similar questions"
// eslint-disable-next-line no-console variant="primary"
console.log('hi!'); onClick={() => {
setContentToCheck(questionContent);
}} }}
/> />
</div> */} <div className="flex flex-col gap-y-2">
{similarQuestions?.map((question) => (
<SimilarQuestionCard
key={question.id}
content="Given an array of integers nums and an integer target, return indices of the two numbers such that they add up. Given an array of integers nums and an integer target, return indices"
questionId={question.id}
timestamp="Today"
type="CODING"
onSimilarQuestionClick={() => {
// eslint-disable-next-line no-console
console.log('hi!');
}}
/>
))}
</div>
<div <div
className="bg-primary-50 flex w-full flex-col gap-y-2 py-3 shadow-[0_0_0_100vmax_theme(colors.primary.50)] sm:flex-row sm:justify-between" className="bg-primary-50 flex w-full flex-col gap-y-2 py-3 shadow-[0_0_0_100vmax_theme(colors.primary.50)] sm:flex-row sm:justify-between"
style={{ style={{
// Hack to make the background bleed outside the container // Hack to make the background bleed outside the container
clipPath: 'inset(0 -100vmax)', clipPath: 'inset(0 -100vmax)',
}}> }}>
<div className="my-2 flex sm:my-0"> <div className="my-2 flex items-center sm:my-0">
<CheckboxInput <CheckboxInput
label="I have checked that my question is new" disabled={questionContent !== contentToCheck}
value={canSubmit} 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} onChange={handleCheckSimilarQuestions}
/> />
</div> </div>
@ -194,7 +238,7 @@ export default function ContributeQuestionForm({
</button> </button>
<Button <Button
className="bg-primary-600 hover:bg-primary-700 focus:ring-primary-500 inline-flex w-full justify-center rounded-md border border-transparent px-4 py-2 text-base font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:bg-slate-400 sm:ml-3 sm:w-auto sm:text-sm" className="bg-primary-600 hover:bg-primary-700 focus:ring-primary-500 inline-flex w-full justify-center rounded-md border border-transparent px-4 py-2 text-base font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:bg-slate-400 sm:ml-3 sm:w-auto sm:text-sm"
disabled={!canSubmit} disabled={!checkedSimilar}
label="Contribute" label="Contribute"
type="submit" type="submit"
variant="primary"></Button> variant="primary"></Button>

@ -1,4 +1,5 @@
import { z } from 'zod'; import { z } from 'zod';
import type { QuestionsQuestion } from '@prisma/client';
import { QuestionsQuestionType, Vote } from '@prisma/client'; import { QuestionsQuestionType, Vote } from '@prisma/client';
import { TRPCError } from '@trpc/server'; import { TRPCError } from '@trpc/server';
@ -207,14 +208,18 @@ export const questionsQuestionRouter = createProtectedRouter()
.split(/\s+/) .split(/\s+/)
.join(' | '); .join(' | ');
const relatedQuestions = await ctx.prisma.$queryRaw` const relatedQuestions = (await ctx.prisma.$queryRaw`
SELECT * FROM "QuestionsQuestion" SELECT * FROM "QuestionsQuestion"
WHERE WHERE "contentSearch" @@ to_tsquery(${query})
"contentSearch" @@ to_tsquery('english', ${query}) ORDER BY ts_rank("contentSearch", to_tsquery(${query})) DESC
ORDER BY ts_rank("textSearch", to_tsquery('english', ${query})) DESC `) as Array<QuestionsQuestion>;
`;
return relatedQuestions; // Dummy data to make this return something
return await ctx.prisma.questionsQuestion.findMany({
take: 5,
});
// Return relatedQuestions;
}, },
}) })
.mutation('create', { .mutation('create', {

Loading…
Cancel
Save