From e0462f3651ad770acbbb545a6666949b22e2e47a Mon Sep 17 00:00:00 2001 From: Jeff Sieu Date: Tue, 25 Oct 2022 02:43:16 +0800 Subject: [PATCH] [questions][feat] view, delete questions in list --- .../questions/AddToListDropdown.tsx | 101 ++++++++++-- .../card/question/BaseQuestionCard.tsx | 2 +- apps/portal/src/pages/questions/lists.tsx | 107 ++++++------- .../server/router/questions-list-router.ts | 97 +++++++++-- .../router/questions-question-router.ts | 150 ++---------------- .../server/createQuestionWithAggregateData.ts | 92 +++++++++++ 6 files changed, 329 insertions(+), 220 deletions(-) create mode 100644 apps/portal/src/utils/questions/server/createQuestionWithAggregateData.ts diff --git a/apps/portal/src/components/questions/AddToListDropdown.tsx b/apps/portal/src/components/questions/AddToListDropdown.tsx index d95c3751..ac58b1eb 100644 --- a/apps/portal/src/components/questions/AddToListDropdown.tsx +++ b/apps/portal/src/components/questions/AddToListDropdown.tsx @@ -1,17 +1,54 @@ -import { forwardRef, Fragment, useEffect, useRef, useState } from 'react'; +import clsx from 'clsx'; +import type { PropsWithChildren } from 'react'; +import { useMemo } from 'react'; +import { Fragment, useRef, useState } from 'react'; import { Menu, Transition } from '@headlessui/react'; import { CheckIcon, HeartIcon } from '@heroicons/react/20/solid'; -import { lists } from '~/pages/questions/lists'; +import { trpc } from '~/utils/trpc'; -function classNames(...classes: Array) { - return classes.filter(Boolean).join(' '); -} +export type AddToListDropdownProps = { + questionId: string; +}; -export default function AddToListDropdown() { +export default function AddToListDropdown({ + questionId, +}: AddToListDropdownProps) { const [menuOpened, setMenuOpened] = useState(false); const ref = useRef() as React.MutableRefObject; + const utils = trpc.useContext(); + const { data: lists } = trpc.useQuery(['questions.lists.getListsByUser']); + + const listsWithQuestionData = useMemo(() => { + return lists?.map((list) => ({ + ...list, + hasQuestion: list.questionEntries.some( + (entry) => entry.question.id === questionId, + ), + })); + }, [lists, questionId]); + + const { mutateAsync: addQuestionToList } = trpc.useMutation( + 'questions.lists.createQuestionEntry', + { + // TODO: Add optimistic update + onSuccess: () => { + utils.invalidateQueries(['questions.lists.getListsByUser']); + }, + }, + ); + + const { mutateAsync: removeQuestionFromList } = trpc.useMutation( + 'questions.lists.deleteQuestionEntry', + { + // TODO: Add optimistic update + onSuccess: () => { + utils.invalidateQueries(['questions.lists.getListsByUser']); + }, + }, + ); + const addClickOutsideListener = () => { document.addEventListener('click', handleClickOutside, true); }; @@ -23,7 +60,32 @@ export default function AddToListDropdown() { } }; - const CustomMenuButton = ({ children }: any) => ( + const handleAddToList = async (listId: string) => { + await addQuestionToList({ + listId, + questionId, + }); + }; + + const handleDeleteFromList = async (listId: string) => { + const list = listsWithQuestionData?.find( + (listWithQuestion) => listWithQuestion.id === listId, + ); + if (!list) { + return; + } + const entry = list.questionEntries.find( + (questionEntry) => questionEntry.question.id === questionId, + ); + if (!entry) { + return; + } + await removeQuestionFromList({ + id: entry.id, + }); + }; + + const CustomMenuButton = ({ children }: PropsWithChildren) => ( )} diff --git a/apps/portal/src/components/questions/card/question/BaseQuestionCard.tsx b/apps/portal/src/components/questions/card/question/BaseQuestionCard.tsx index 058b5184..e6dd0167 100644 --- a/apps/portal/src/components/questions/card/question/BaseQuestionCard.tsx +++ b/apps/portal/src/components/questions/card/question/BaseQuestionCard.tsx @@ -154,7 +154,7 @@ export default function BaseQuestionCard({

{timestamp}

{showAddToList && (
- +
)} diff --git a/apps/portal/src/pages/questions/lists.tsx b/apps/portal/src/pages/questions/lists.tsx index 721d5b84..ea4009f8 100644 --- a/apps/portal/src/pages/questions/lists.tsx +++ b/apps/portal/src/pages/questions/lists.tsx @@ -14,28 +14,9 @@ import DeleteListDialog from '~/components/questions/DeleteListDialog'; import { Button } from '~/../../../packages/ui/dist'; import { APP_TITLE } from '~/utils/questions/constants'; -import { SAMPLE_QUESTION } from '~/utils/questions/constants'; import createSlug from '~/utils/questions/createSlug'; import { trpc } from '~/utils/trpc'; -export const questions = [ - SAMPLE_QUESTION, - SAMPLE_QUESTION, - SAMPLE_QUESTION, - SAMPLE_QUESTION, - SAMPLE_QUESTION, - SAMPLE_QUESTION, - SAMPLE_QUESTION, - SAMPLE_QUESTION, - SAMPLE_QUESTION, - SAMPLE_QUESTION, - SAMPLE_QUESTION, - SAMPLE_QUESTION, - SAMPLE_QUESTION, - SAMPLE_QUESTION, - SAMPLE_QUESTION, -]; - export default function ListPage() { const utils = trpc.useContext(); const { data: lists } = trpc.useQuery(['questions.lists.getListsByUser']); @@ -57,10 +38,17 @@ export default function ListPage() { }, }, ); - - const [selectedList, setSelectedList] = useState( - lists?.length ? lists[0].id : '', + const { mutateAsync: deleteQuestionEntry } = trpc.useMutation( + 'questions.lists.deleteQuestionEntry', + { + onSuccess: () => { + // TODO: Add optimistic update + utils.invalidateQueries(['questions.lists.getListsByUser']); + }, + }, ); + + const [selectedListIndex, setSelectedListIndex] = useState(0); const [showDeleteListDialog, setShowDeleteListDialog] = useState(false); const [showCreateListDialog, setShowCreateListDialog] = useState(false); @@ -91,19 +79,17 @@ export default function ListPage() { const listOptions = ( <>
    - {(lists ?? []).map((list) => ( + {(lists ?? []).map((list, index) => (