diff --git a/src/features/feedback/components/DashboardFilterBar/DashboardFilterBar.tsx b/src/features/feedback/components/DashboardFilterBar/DashboardFilterBar.tsx new file mode 100644 index 00000000..f5e8a0cd --- /dev/null +++ b/src/features/feedback/components/DashboardFilterBar/DashboardFilterBar.tsx @@ -0,0 +1,31 @@ +import { Flex, Spacer } from '@chakra-ui/react' +import { useFilterFeedback } from '../../api/useFilterFeedback' +import { FilterSearchbar } from './FilterSearchbar' +import { FilterSelect } from './FilterSelect' + +export const DashboardFilterBar = (): JSX.Element => { + const { filter, order, handleFilterChange, handleOrderChange, isLoading } = + useFilterFeedback() + + return ( + + + + + + + ) +} diff --git a/src/features/feedback/components/DashboardFilterBar/FilterSearchbar.tsx b/src/features/feedback/components/DashboardFilterBar/FilterSearchbar.tsx new file mode 100644 index 00000000..14dba6c2 --- /dev/null +++ b/src/features/feedback/components/DashboardFilterBar/FilterSearchbar.tsx @@ -0,0 +1,24 @@ +import { Box } from '@chakra-ui/react' +import { Searchbar } from '@opengovsg/design-system-react' +import { useRouter } from 'next/router' +import { useCallback } from 'react' + +export const FilterSearchbar = (): JSX.Element => { + const router = useRouter() + const handleSearch = useCallback( + (value: string) => { + router.push(`/search?q=${value}`) + }, + [router] + ) + + return ( + + + + ) +} diff --git a/src/features/feedback/components/TeamFeedbackFilterBar.tsx b/src/features/feedback/components/DashboardFilterBar/FilterSelect.tsx similarity index 62% rename from src/features/feedback/components/TeamFeedbackFilterBar.tsx rename to src/features/feedback/components/DashboardFilterBar/FilterSelect.tsx index a6622393..eb62bdfd 100644 --- a/src/features/feedback/components/TeamFeedbackFilterBar.tsx +++ b/src/features/feedback/components/DashboardFilterBar/FilterSelect.tsx @@ -1,12 +1,10 @@ import { - Box, - MenuItemOption, + Menu, MenuList, MenuOptionGroup, - Menu, + MenuItemOption, } from '@chakra-ui/react' import { ChevronMenuButton } from '~/components/ChevronMenuButton' -import { useFilterFeedback } from '../api/useFilterFeedback' interface FilterSelectProps { selection: { @@ -17,7 +15,7 @@ interface FilterSelectProps { onChange: (value: string | string[]) => void isDisabled?: boolean } -const FilterSelect = ({ +export const FilterSelect = ({ selection, onChange, isDisabled, @@ -47,28 +45,3 @@ const FilterSelect = ({ ) } - -export const TeamFeedbackFilterBar = (): JSX.Element => { - const { filter, order, handleFilterChange, handleOrderChange, isLoading } = - useFilterFeedback() - - return ( - - - - - ) -} diff --git a/src/features/feedback/components/DashboardFilterBar/index.ts b/src/features/feedback/components/DashboardFilterBar/index.ts new file mode 100644 index 00000000..dd4a6dba --- /dev/null +++ b/src/features/feedback/components/DashboardFilterBar/index.ts @@ -0,0 +1 @@ +export { DashboardFilterBar } from './DashboardFilterBar' diff --git a/src/features/feedback/components/index.ts b/src/features/feedback/components/index.ts index 035432f3..0eeb484d 100644 --- a/src/features/feedback/components/index.ts +++ b/src/features/feedback/components/index.ts @@ -2,3 +2,4 @@ export * from './FeedbackNavbar' export * from './TeamFeedbackList' export * from './FeedbackComment' export * from './FeedbackActionsModal' +export * from './DashboardFilterBar' diff --git a/src/pages/dashboard.tsx b/src/pages/dashboard.tsx index 6bd01f0b..1a272aec 100644 --- a/src/pages/dashboard.tsx +++ b/src/pages/dashboard.tsx @@ -7,11 +7,11 @@ import feedbackUncleSvg from '~/features/feedback/assets/feedback-uncle.svg' import { FeedbackActionsModal, TeamFeedbackList, + DashboardFilterBar, } from '~/features/feedback/components' import type { NextPageWithLayout } from '~/lib/types' import { AdminLayout } from '~/templates/layouts/AdminLayout' import { trpc } from '~/utils/trpc' -import { TeamFeedbackFilterBar } from '~/features/feedback/components/TeamFeedbackFilterBar' const Dashboard: NextPageWithLayout = () => { const { data: counts, isLoading: unreadCountIsLoading } = @@ -55,7 +55,7 @@ const Dashboard: NextPageWithLayout = () => { borderWidth="1px" borderColor="base.divider.medium" > - + diff --git a/src/pages/search.tsx b/src/pages/search.tsx new file mode 100644 index 00000000..a4ac159c --- /dev/null +++ b/src/pages/search.tsx @@ -0,0 +1,63 @@ +import { Box, Text } from '@chakra-ui/react' +import { Searchbar } from '@opengovsg/design-system-react' +import type { GetServerSideProps, InferGetServerSidePropsType } from 'next' +import { useRouter } from 'next/router' +import { useCallback, useState } from 'react' +import { FeedbackComment } from '~/features/feedback/components' +import type { NextPageWithLayout } from '~/lib/types' +import { AdminLayout } from '~/templates/layouts/AdminLayout' +import { trpc } from '~/utils/trpc' + +const Search: NextPageWithLayout< + InferGetServerSidePropsType +> = ({ query }) => { + const router = useRouter() + const [queryInput, setQueryInput] = useState(query) + const { data, isLoading } = trpc.post.search.useQuery({ query }) + + const updateSearchQuery = useCallback( + (newQuery: string) => { + if (!newQuery) return + router.query.q = newQuery + router.push(router) + }, + [router] + ) + + return ( + + + setQueryInput(e.target.value)} + onSearch={updateSearchQuery} + /> + + {isLoading ? 'Searching...' : `${data?.length} results`} + {data?.map((d) => ( + + ))} + + ) +} + +Search.getLayout = AdminLayout + +export const getServerSideProps: GetServerSideProps<{ + query: string +}> = async ({ query }) => { + if (!query.q) { + return { + redirect: { + permanent: false, + destination: '/', + }, + } + } + return { + props: { query: String(query.q) }, + } +} + +export default Search diff --git a/src/server/modules/post/post.router.ts b/src/server/modules/post/post.router.ts index 19ff1dd7..8c735275 100644 --- a/src/server/modules/post/post.router.ts +++ b/src/server/modules/post/post.router.ts @@ -186,6 +186,26 @@ export const postRouter = router({ }) return updatedPost }), + search: protectedProcedure + .input( + z.object({ + query: z.string(), + limit: z.number().min(1).max(30).optional().default(10), + }) + ) + .query(async ({ input: { query, limit }, ctx }) => { + return await ctx.prisma.post.findMany({ + take: limit, + where: { + deletedAt: null, + OR: [ + { title: { contains: query, mode: 'insensitive' } }, + { content: { contains: query, mode: 'insensitive' } }, + ], + }, + select: defaultPostSelect, + }) + }), delete: protectedProcedure .input(z.object({ id: z.string() })) .mutation(async ({ input: { id }, ctx }) => {