diff --git a/apps/portal/prisma/schema.prisma b/apps/portal/prisma/schema.prisma
index af2ecc0e..59c53a40 100644
--- a/apps/portal/prisma/schema.prisma
+++ b/apps/portal/prisma/schema.prisma
@@ -204,8 +204,8 @@ model QuestionsQuestionEncounter {
userId String?
// TODO: sync with models
company String @db.Text
- location String? @db.Text
- role String? @db.Text
+ location String @db.Text
+ role String @db.Text
seenAt DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
diff --git a/apps/portal/src/components/questions/ContributeQuestionForm.tsx b/apps/portal/src/components/questions/ContributeQuestionForm.tsx
index 4215bae2..367f0d88 100644
--- a/apps/portal/src/components/questions/ContributeQuestionForm.tsx
+++ b/apps/portal/src/components/questions/ContributeQuestionForm.tsx
@@ -3,17 +3,10 @@ import { useForm } from 'react-hook-form';
import {
BuildingOffice2Icon,
CalendarDaysIcon,
- // UserIcon,
+ UserIcon,
} from '@heroicons/react/24/outline';
import type { QuestionsQuestionType } from '@prisma/client';
-import {
- Button,
- Collapsible,
- Select,
- // HorizontalDivider,
- TextArea,
- TextInput,
-} from '@tih/ui';
+import { Button, Collapsible, Select, TextArea, TextInput } from '@tih/ui';
import { QUESTION_TYPES } from '~/utils/questions/constants';
import {
@@ -21,7 +14,6 @@ import {
useSelectRegister,
} from '~/utils/questions/useFormRegister';
-// Import SimilarQuestionCard from './card/SimilarQuestionCard';
import Checkbox from './ui-patch/Checkbox';
export type ContributeQuestionData = {
@@ -31,6 +23,7 @@ export type ContributeQuestionData = {
position: string;
questionContent: string;
questionType: QuestionsQuestionType;
+ role: string;
};
export type ContributeQuestionFormProps = {
@@ -99,19 +92,21 @@ export default function ContributeQuestionForm({
- {/*
{/*
diff --git a/apps/portal/src/components/questions/LandingComponent.tsx b/apps/portal/src/components/questions/LandingComponent.tsx
index 98614794..7f3ffee4 100644
--- a/apps/portal/src/components/questions/LandingComponent.tsx
+++ b/apps/portal/src/components/questions/LandingComponent.tsx
@@ -1,5 +1,6 @@
import { useState } from 'react';
import { ArrowSmallRightIcon } from '@heroicons/react/24/outline';
+import type { QuestionsQuestionType } from '@prisma/client';
import { Button, Select } from '@tih/ui';
import {
@@ -11,7 +12,7 @@ import {
export type LandingQueryData = {
company: string;
location: string;
- questionType: string;
+ questionType: QuestionsQuestionType;
};
export type LandingComponentProps = {
@@ -22,9 +23,9 @@ export default function LandingComponent({
onLanded: handleLandingQuery,
}: LandingComponentProps) {
const [landingQueryData, setLandingQueryData] = useState({
- company: 'google',
- location: 'singapore',
- questionType: 'coding',
+ company: 'Google',
+ location: 'Singapore',
+ questionType: 'CODING',
});
const handleChangeCompany = (company: string) => {
@@ -35,7 +36,7 @@ export default function LandingComponent({
setLandingQueryData((prev) => ({ ...prev, location }));
};
- const handleChangeType = (questionType: string) => {
+ const handleChangeType = (questionType: QuestionsQuestionType) => {
setLandingQueryData((prev) => ({ ...prev, questionType }));
};
@@ -61,7 +62,9 @@ export default function LandingComponent({
label="Type"
options={QUESTION_TYPES}
value={landingQueryData.questionType}
- onChange={handleChangeType}
+ onChange={(value) => {
+ handleChangeType(value.toUpperCase() as QuestionsQuestionType);
+ }}
/>
questions from
diff --git a/apps/portal/src/components/questions/filter/FilterSection.tsx b/apps/portal/src/components/questions/filter/FilterSection.tsx
index f7b4f800..cfb6ba5c 100644
--- a/apps/portal/src/components/questions/filter/FilterSection.tsx
+++ b/apps/portal/src/components/questions/filter/FilterSection.tsx
@@ -10,7 +10,7 @@ export type FilterOption = {
value: V;
};
-export type FilterChoices = Array<
+export type FilterChoices = ReadonlyArray<
Omit, 'checked'>
>;
diff --git a/apps/portal/src/pages/questions/index.tsx b/apps/portal/src/pages/questions/index.tsx
index 331ef57b..826a6204 100644
--- a/apps/portal/src/pages/questions/index.tsx
+++ b/apps/portal/src/pages/questions/index.tsx
@@ -1,3 +1,4 @@
+import { subMonths, subYears } from 'date-fns';
import { useRouter } from 'next/router';
import { useEffect, useMemo, useState } from 'react';
import type { QuestionsQuestionType } from '@prisma/client';
@@ -9,6 +10,7 @@ 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,
@@ -31,24 +33,41 @@ export default function QuestionsHomePage() {
selectedQuestionTypes,
setSelectedQuestionTypes,
areQuestionTypesInitialized,
- ] = useSearchFilter('questionTypes');
+ ] = useSearchFilter('questionTypes', {
+ queryParamToValue: (param) => {
+ return param.toUpperCase() as QuestionsQuestionType;
+ },
+ });
const [
selectedQuestionAge,
setSelectedQuestionAge,
isQuestionAgeInitialized,
- ] = useSearchFilterSingle('questionAge', 'all');
+ ] = useSearchFilterSingle('questionAge', {
+ defaultValue: 'all',
+ });
const [selectedLocations, setSelectedLocations, areLocationsInitialized] =
useSearchFilter('locations');
- // TODO: Implement filtering
+ 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',
{
- // TODO: Update when query accepts multiple question types
- questionType:
- selectedQuestionTypes.length > 0
- ? (selectedQuestionTypes[0].toUpperCase() as QuestionsQuestionType)
- : 'CODING',
+ companies: selectedCompanies,
+ endDate: today,
+ locations: selectedLocations,
+ questionTypes: selectedQuestionTypes,
+ roles: [],
+ startDate,
},
]);
@@ -214,6 +233,7 @@ export default function QuestionsHomePage() {
content: data.questionContent,
location: data.location,
questionType: data.questionType,
+ role: data.role,
seenAt: data.date,
});
}}
diff --git a/apps/portal/src/server/router/questions-question-router.ts b/apps/portal/src/server/router/questions-question-router.ts
index bd65474a..39dafd98 100644
--- a/apps/portal/src/server/router/questions-question-router.ts
+++ b/apps/portal/src/server/router/questions-question-router.ts
@@ -9,10 +9,12 @@ import type { Question } from '~/types/questions';
export const questionsQuestionRouter = createProtectedRouter()
.query('getQuestionsByFilter', {
input: z.object({
- company: z.string().optional(),
- location: z.string().optional(),
- questionType: z.nativeEnum(QuestionsQuestionType),
- role: z.string().optional(),
+ companies: z.string().array(),
+ endDate: z.date(),
+ locations: z.string().array(),
+ questionTypes: z.nativeEnum(QuestionsQuestionType).array(),
+ roles: z.string().array(),
+ startDate: z.date().optional(),
}),
async resolve({ ctx, input }) {
const questionsData = await ctx.prisma.questionsQuestion.findMany({
@@ -42,7 +44,13 @@ export const questionsQuestionRouter = createProtectedRouter()
createdAt: 'desc',
},
where: {
- questionType: input.questionType,
+ ...(input.questionTypes.length > 0
+ ? {
+ questionType: {
+ in: input.questionTypes,
+ },
+ }
+ : {}),
},
});
return questionsData
@@ -50,11 +58,17 @@ export const questionsQuestionRouter = createProtectedRouter()
for (let i = 0; i < data.encounters.length; i++) {
const encounter = data.encounters[i];
const matchCompany =
- !input.company || encounter.company === input.company;
+ input.companies.length === 0 ||
+ input.companies.includes(encounter.company);
const matchLocation =
- !input.location || encounter.location === input.location;
- const matchRole = !input.company || encounter.role === input.role;
- if (matchCompany && matchLocation && matchRole) {
+ input.locations.length === 0 ||
+ input.locations.includes(encounter.location);
+ const matchRole =
+ input.roles.length === 0 || input.roles.includes(encounter.role);
+ const matchDate =
+ (!input.startDate || encounter.seenAt >= input.startDate) &&
+ encounter.seenAt <= input.endDate;
+ if (matchCompany && matchLocation && matchRole && matchDate) {
return true;
}
}
@@ -174,7 +188,7 @@ export const questionsQuestionRouter = createProtectedRouter()
content: z.string(),
location: z.string(),
questionType: z.nativeEnum(QuestionsQuestionType),
- role: z.string().optional(),
+ role: z.string(),
seenAt: z.date(),
}),
async resolve({ ctx, input }) {
@@ -183,6 +197,17 @@ export const questionsQuestionRouter = createProtectedRouter()
const question = await ctx.prisma.questionsQuestion.create({
data: {
content: input.content,
+ encounters: {
+ create: [
+ {
+ company: input.company,
+ location: input.location,
+ role: input.role,
+ seenAt: input.seenAt,
+ userId,
+ },
+ ],
+ },
questionType: input.questionType,
userId,
},
diff --git a/apps/portal/src/utils/questions/constants.ts b/apps/portal/src/utils/questions/constants.ts
index 155359d5..357b6f55 100644
--- a/apps/portal/src/utils/questions/constants.ts
+++ b/apps/portal/src/utils/questions/constants.ts
@@ -5,13 +5,13 @@ import type { FilterChoices } from '~/components/questions/filter/FilterSection'
export const COMPANIES: FilterChoices = [
{
label: 'Google',
- value: 'google',
+ value: 'Google',
},
{
label: 'Meta',
- value: 'meta',
+ value: 'Meta',
},
-];
+] as const;
// Code, design, behavioral
export const QUESTION_TYPES: FilterChoices = [
@@ -27,9 +27,11 @@ export const QUESTION_TYPES: FilterChoices = [
label: 'Behavioral',
value: 'BEHAVIORAL',
},
-];
+] as const;
-export const QUESTION_AGES: FilterChoices = [
+export type QuestionAge = 'all' | 'last-6-months' | 'last-month' | 'last-year';
+
+export const QUESTION_AGES: FilterChoices = [
{
label: 'Last month',
value: 'last-month',
@@ -46,16 +48,16 @@ export const QUESTION_AGES: FilterChoices = [
label: 'All',
value: 'all',
},
-];
+] as const;
export const LOCATIONS: FilterChoices = [
{
label: 'Singapore',
- value: 'singapore',
+ value: 'Singapore',
},
{
label: 'Menlo Park',
- value: 'menlopark',
+ value: 'Menlo Park',
},
{
label: 'California',
@@ -63,13 +65,13 @@ export const LOCATIONS: FilterChoices = [
},
{
label: 'Hong Kong',
- value: 'hongkong',
+ value: 'Hong Kong',
},
{
label: 'Taiwan',
- value: 'taiwan',
+ value: 'Taiwan',
},
-];
+] as const;
export const SAMPLE_QUESTION = {
answerCount: 10,
diff --git a/apps/portal/src/utils/questions/useSearchFilter.ts b/apps/portal/src/utils/questions/useSearchFilter.ts
index 392a69c8..1a7bd199 100644
--- a/apps/portal/src/utils/questions/useSearchFilter.ts
+++ b/apps/portal/src/utils/questions/useSearchFilter.ts
@@ -3,8 +3,12 @@ import { useCallback, useEffect, useState } from 'react';
export const useSearchFilter = (
name: string,
- defaultValues?: Array,
+ opts: {
+ defaultValues?: Array;
+ queryParamToValue?: (param: string) => Value;
+ } = {},
) => {
+ const { defaultValues, queryParamToValue = (param) => param } = opts;
const [isInitialized, setIsInitialized] = useState(false);
const router = useRouter();
@@ -16,7 +20,7 @@ export const useSearchFilter = (
const query = router.query[name];
if (query) {
const queryValues = Array.isArray(query) ? query : [query];
- setFilters(queryValues as Array);
+ setFilters(queryValues.map(queryParamToValue) as Array);
} else {
// Try to load from local storage
const localStorageValue = localStorage.getItem(name);
@@ -34,7 +38,7 @@ export const useSearchFilter = (
}
setIsInitialized(true);
}
- }, [isInitialized, name, router]);
+ }, [isInitialized, name, queryParamToValue, router]);
const setFiltersCallback = useCallback(
(newFilters: Array) => {
@@ -56,11 +60,16 @@ export const useSearchFilter = (
export const useSearchFilterSingle = (
name: string,
- defaultValue: Value,
+ opts: {
+ defaultValue?: Value;
+ queryParamToValue?: (param: string) => Value;
+ } = {},
) => {
- const [filters, setFilters, isInitialized] = useSearchFilter(name, [
- defaultValue,
- ]);
+ const { defaultValue, queryParamToValue } = opts;
+ const [filters, setFilters, isInitialized] = useSearchFilter(name, {
+ defaultValues: defaultValue !== undefined ? [defaultValue] : undefined,
+ queryParamToValue,
+ });
return [
filters[0],