From 1c20e2c7b1b528fc2bb2c529855e8df9a5835d72 Mon Sep 17 00:00:00 2001 From: wlren Date: Wed, 19 Oct 2022 16:20:35 +0800 Subject: [PATCH] [questions][ui] add hover --- .../components/questions/card/AnswerCard.tsx | 7 +- .../questions/card/QuestionAnswerCard.tsx | 4 +- .../questions/card/QuestionCard.tsx | 7 +- .../questions/card/QuestionOverviewCard.tsx | 1 + apps/portal/src/pages/questions/index.tsx | 4 +- apps/portal/src/pages/questions/lists.tsx | 332 ++++++++++++++++++ 6 files changed, 348 insertions(+), 7 deletions(-) create mode 100644 apps/portal/src/pages/questions/lists.tsx diff --git a/apps/portal/src/components/questions/card/AnswerCard.tsx b/apps/portal/src/components/questions/card/AnswerCard.tsx index 20818ee7..5407e95e 100644 --- a/apps/portal/src/components/questions/card/AnswerCard.tsx +++ b/apps/portal/src/components/questions/card/AnswerCard.tsx @@ -13,6 +13,7 @@ export type AnswerCardProps = { commentCount?: number; content: string; createdAt: Date; + showHover?: boolean; upvoteCount: number; votingButtonsSize: VotingButtonsProps['size']; }; @@ -26,10 +27,14 @@ export default function AnswerCard({ commentCount, votingButtonsSize, upvoteCount, + showHover, }: AnswerCardProps) { const { handleUpvote, handleDownvote, vote } = useAnswerVote(answerId); + const hoverClass = showHover ? 'hover:bg-slate-50' : ''; + return ( -
+
+ Omit >; function QuestionAnswerCardWithoutHref(props: QuestionAnswerCardProps) { - return ; + return ; } const QuestionAnswerCard = withHref(QuestionAnswerCardWithoutHref); diff --git a/apps/portal/src/components/questions/card/QuestionCard.tsx b/apps/portal/src/components/questions/card/QuestionCard.tsx index ba530f75..9677fc5f 100644 --- a/apps/portal/src/components/questions/card/QuestionCard.tsx +++ b/apps/portal/src/components/questions/card/QuestionCard.tsx @@ -48,6 +48,7 @@ export type QuestionCardProps = ActionButtonProps & questionId: string; receivedCount: number; role: string; + showHover?: boolean; timestamp: string; type: QuestionsQuestionType; }; @@ -68,11 +69,13 @@ export default function QuestionCard({ timestamp, role, location, + showHover, }: QuestionCardProps) { const { handleDownvote, handleUpvote, vote } = useQuestionVote(questionId); - + const hoverClass = showHover ? 'hover:bg-slate-50' : ''; return ( -
+
{showVoteButtons && ( diff --git a/apps/portal/src/pages/questions/index.tsx b/apps/portal/src/pages/questions/index.tsx index 69caac9f..eed5464f 100644 --- a/apps/portal/src/pages/questions/index.tsx +++ b/apps/portal/src/pages/questions/index.tsx @@ -297,8 +297,8 @@ export default function QuestionsHomePage() { timestamp={question.seenAt.toLocaleDateString(undefined, { month: 'short', year: 'numeric', - })} - type={question.type} // TODO: Implement received count + })} // TODO: Implement received count + type={question.type} upvoteCount={question.numVotes} /> ))} diff --git a/apps/portal/src/pages/questions/lists.tsx b/apps/portal/src/pages/questions/lists.tsx new file mode 100644 index 00000000..93ac9725 --- /dev/null +++ b/apps/portal/src/pages/questions/lists.tsx @@ -0,0 +1,332 @@ +import { subMonths, subYears } from 'date-fns'; +import Router, { useRouter } from 'next/router'; +import { useEffect, useMemo, useState } from 'react'; +import { NoSymbolIcon } from '@heroicons/react/24/outline'; +import type { QuestionsQuestionType } from '@prisma/client'; +import { SlideOut } from '@tih/ui'; + +import QuestionOverviewCard from '~/components/questions/card/QuestionOverviewCard'; +import ContributeQuestionCard from '~/components/questions/ContributeQuestionCard'; +import FilterSection from '~/components/questions/filter/FilterSection'; +import type { LandingQueryData } from '~/components/questions/LandingComponent'; +import LandingComponent from '~/components/questions/LandingComponent'; +import QuestionSearchBar from '~/components/questions/QuestionSearchBar'; + +import type { QuestionAge } from '~/utils/questions/constants'; +import { + COMPANIES, + LOCATIONS, + QUESTION_AGES, + QUESTION_TYPES, +} from '~/utils/questions/constants'; +import createSlug from '~/utils/questions/createSlug'; +import { + useSearchFilter, + useSearchFilterSingle, +} from '~/utils/questions/useSearchFilter'; +import { trpc } from '~/utils/trpc'; + +export default function QuestionsHomePage() { + const router = useRouter(); + + const [selectedCompanies, setSelectedCompanies, areCompaniesInitialized] = + useSearchFilter('companies'); + const [ + selectedQuestionTypes, + setSelectedQuestionTypes, + areQuestionTypesInitialized, + ] = useSearchFilter('questionTypes', { + queryParamToValue: (param) => { + return param.toUpperCase() as QuestionsQuestionType; + }, + }); + const [ + selectedQuestionAge, + setSelectedQuestionAge, + isQuestionAgeInitialized, + ] = useSearchFilterSingle('questionAge', { + defaultValue: 'all', + }); + const [selectedLocations, setSelectedLocations, areLocationsInitialized] = + useSearchFilter('locations'); + + const today = useMemo(() => new Date(), []); + const startDate = useMemo(() => { + return selectedQuestionAge === 'last-year' + ? subYears(new Date(), 1) + : selectedQuestionAge === 'last-6-months' + ? subMonths(new Date(), 6) + : selectedQuestionAge === 'last-month' + ? subMonths(new Date(), 1) + : undefined; + }, [selectedQuestionAge]); + + const { data: questions } = trpc.useQuery( + [ + 'questions.questions.getQuestionsByFilter', + { + companies: selectedCompanies, + endDate: today, + locations: selectedLocations, + questionTypes: selectedQuestionTypes, + roles: [], + startDate, + }, + ], + { + keepPreviousData: true, + }, + ); + + const utils = trpc.useContext(); + const { mutate: createQuestion } = trpc.useMutation( + 'questions.questions.create', + { + onSuccess: () => { + utils.invalidateQueries('questions.questions.getQuestionsByFilter'); + }, + }, + ); + + const [hasLanded, setHasLanded] = useState(false); + const [loaded, setLoaded] = useState(false); + const [filterDrawerOpen, setFilterDrawerOpen] = useState(false); + + const companyFilterOptions = useMemo(() => { + return COMPANIES.map((company) => ({ + ...company, + checked: selectedCompanies.includes(company.value), + })); + }, [selectedCompanies]); + + const questionTypeFilterOptions = useMemo(() => { + return QUESTION_TYPES.map((questionType) => ({ + ...questionType, + checked: selectedQuestionTypes.includes(questionType.value), + })); + }, [selectedQuestionTypes]); + + const questionAgeFilterOptions = useMemo(() => { + return QUESTION_AGES.map((questionAge) => ({ + ...questionAge, + checked: selectedQuestionAge === questionAge.value, + })); + }, [selectedQuestionAge]); + + const locationFilterOptions = useMemo(() => { + return LOCATIONS.map((location) => ({ + ...location, + checked: selectedLocations.includes(location.value), + })); + }, [selectedLocations]); + + const handleLandingQuery = async (data: LandingQueryData) => { + const { company, location, questionType } = data; + + setSelectedCompanies([company]); + setSelectedLocations([location]); + setSelectedQuestionTypes([questionType as QuestionsQuestionType]); + setHasLanded(true); + }; + + const areFiltersInitialized = useMemo(() => { + return ( + areCompaniesInitialized && + areQuestionTypesInitialized && + isQuestionAgeInitialized && + areLocationsInitialized + ); + }, [ + areCompaniesInitialized, + areQuestionTypesInitialized, + isQuestionAgeInitialized, + areLocationsInitialized, + ]); + + const { pathname } = router; + useEffect(() => { + if (areFiltersInitialized) { + // Router.replace used instead of router.replace to avoid + // the page reloading itself since the router.replace + // callback changes on every page load + Router.replace({ + pathname, + query: { + companies: selectedCompanies, + locations: selectedLocations, + questionAge: selectedQuestionAge, + questionTypes: selectedQuestionTypes, + }, + }); + const hasFilter = + selectedCompanies.length > 0 || + selectedLocations.length > 0 || + selectedQuestionAge !== 'all' || + selectedQuestionTypes.length > 0; + if (hasFilter) { + setHasLanded(true); + } + + setLoaded(true); + } + }, [ + areFiltersInitialized, + hasLanded, + loaded, + pathname, + selectedCompanies, + selectedLocations, + selectedQuestionAge, + selectedQuestionTypes, + ]); + + if (!loaded) { + return null; + } + const filterSidebar = ( +
+ { + if (checked) { + setSelectedCompanies([...selectedCompanies, optionValue]); + } else { + setSelectedCompanies( + selectedCompanies.filter((company) => company !== optionValue), + ); + } + }} + /> + { + if (checked) { + setSelectedQuestionTypes([...selectedQuestionTypes, optionValue]); + } else { + setSelectedQuestionTypes( + selectedQuestionTypes.filter( + (questionType) => questionType !== optionValue, + ), + ); + } + }} + /> + { + setSelectedQuestionAge(optionValue); + }} + /> + { + if (checked) { + setSelectedLocations([...selectedLocations, optionValue]); + } else { + setSelectedLocations( + selectedLocations.filter((location) => location !== optionValue), + ); + } + }} + /> +
+ ); + + return !hasLanded ? ( + + ) : ( +
+
+
+
+
+ { + createQuestion({ + company: data.company, + content: data.questionContent, + location: data.location, + questionType: data.questionType, + role: data.role, + seenAt: data.date, + }); + }} + /> + { + setFilterDrawerOpen(!filterDrawerOpen); + }} + onSortChange={(value) => { + // eslint-disable-next-line no-console + console.log(value); + }} + /> + {(questions ?? []).map((question) => ( + + ))} + {questions?.length === 0 && ( +
+ +

Nothing found. Try changing your search filters.

+
+ )} +
+
+
+ + { + setFilterDrawerOpen(false); + }}> + {filterSidebar} + +
+
+ ); +}