diff --git a/apps/customer-service/src/app/tickets/layout-with-sidebar.tsx b/apps/customer-service/src/app/tickets/layout-with-sidebar.tsx index 829dd2624..096da52ae 100644 --- a/apps/customer-service/src/app/tickets/layout-with-sidebar.tsx +++ b/apps/customer-service/src/app/tickets/layout-with-sidebar.tsx @@ -15,6 +15,7 @@ import { } from '~/components/ui/accordion'; import { Sheet, SheetContent } from '~/components/ui/sheet'; import { UserNav } from '~/components/user-nav'; +import { FILTER_QUERY_PARAM } from '~/utils/search-params'; const useNavigationLinks = () => { return [ @@ -34,17 +35,17 @@ const useNavigationLinks = () => { diff --git a/apps/customer-service/src/components/tickets/ticket-list-container.tsx b/apps/customer-service/src/components/tickets/ticket-list-container.tsx new file mode 100644 index 000000000..e81b5d014 --- /dev/null +++ b/apps/customer-service/src/components/tickets/ticket-list-container.tsx @@ -0,0 +1,47 @@ +'use client'; + +import { Suspense } from 'react'; +import { useSearchParams } from 'next/navigation'; + +import { TicketStatus } from '@cs/database/schema/ticket'; + +import { TicketList } from '~/components/tickets/ticket-list'; +import { TicketListHeader } from '~/components/tickets/ticket-list-header'; +import { TicketListItemSkeleton } from '~/components/tickets/ticket-list-item-skeleton'; +import { + FILTER_QUERY_PARAM, + ORDER_BY_QUERY_PARAM, + STATUS_QUERY_PARAM, +} from '~/utils/search-params'; + +export const TicketListContainer = () => { + const searchParams = useSearchParams(); + const filter = + searchParams.get(FILTER_QUERY_PARAM) === 'me' + ? 'me' + : searchParams.get(FILTER_QUERY_PARAM) === 'unassigned' + ? 'unassigned' + : 'all'; + const status = + searchParams.get(STATUS_QUERY_PARAM) === 'resolved' + ? TicketStatus.Resolved + : TicketStatus.Open; + const orderBy = + searchParams.get(ORDER_BY_QUERY_PARAM) === 'oldest' ? 'oldest' : 'newest'; + return ( + + ); +}; diff --git a/apps/customer-service/src/components/tickets/ticket-list-header.tsx b/apps/customer-service/src/components/tickets/ticket-list-header.tsx index 8d6cac94a..96e7ba55d 100644 --- a/apps/customer-service/src/components/tickets/ticket-list-header.tsx +++ b/apps/customer-service/src/components/tickets/ticket-list-header.tsx @@ -1,16 +1,12 @@ 'use client'; import { FC } from 'react'; -import Link from 'next/link'; -import { - ReadonlyURLSearchParams, - usePathname, - useRouter, - useSearchParams, -} from 'next/navigation'; +import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { ChevronDown } from 'lucide-react'; import { FormattedMessage } from 'react-intl'; +import { TicketStatus } from '@cs/database/schema/ticket'; + import { Button } from '~/components/ui/button'; import { DropdownMenu, @@ -19,31 +15,20 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '~/components/ui/dropdown-menu'; +import { + getUpdatedSearchParams, + ORDER_BY_QUERY_PARAM, + STATUS_QUERY_PARAM, +} from '~/utils/search-params'; -export const TicketListHeader: FC = () => { +export const TicketListHeader: FC<{ + status: TicketStatus; + orderBy: 'newest' | 'oldest'; +}> = ({ status, orderBy }) => { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); - const getUpdatedSearchParams = ( - searchParams: ReadonlyURLSearchParams, - oldValue: string, - newValue: string - ) => { - const updatedSeachParams = new URLSearchParams( - Array.from(searchParams.entries()) - ); - - if ( - !updatedSeachParams.get('orderBy') || - updatedSeachParams.get('orderBy') === oldValue - ) { - updatedSeachParams.set('orderBy', newValue); - } - - return updatedSeachParams.toString(); - }; - return (

Inbox

@@ -54,7 +39,59 @@ export const TicketListHeader: FC = () => { variant="secondary" className="flex items-center justify-between gap-x-1 text-sm leading-6" > - + { + { + Resolved: , + Open: , + }[status] + } + + + + + + + router.replace( + `${pathname}?${getUpdatedSearchParams( + searchParams, + STATUS_QUERY_PARAM, + 'open' + ).toString()}` + ) + } + > + + + + router.replace( + `${pathname}?${getUpdatedSearchParams( + searchParams, + STATUS_QUERY_PARAM, + 'resolved' + ).toString()}` + ) + } + > + + + + + + + + @@ -65,9 +102,9 @@ export const TicketListHeader: FC = () => { router.replace( `${pathname}?${getUpdatedSearchParams( searchParams, - 'oldest', + ORDER_BY_QUERY_PARAM, 'newest' - )}` + ).toString()}` ) } > @@ -78,9 +115,9 @@ export const TicketListHeader: FC = () => { router.replace( `${pathname}?${getUpdatedSearchParams( searchParams, - 'newest', + ORDER_BY_QUERY_PARAM, 'oldest' - )}` + ).toString()}` ) } > diff --git a/apps/customer-service/src/components/tickets/ticket-list.tsx b/apps/customer-service/src/components/tickets/ticket-list.tsx index 5527eaf3e..5fa5349e2 100644 --- a/apps/customer-service/src/components/tickets/ticket-list.tsx +++ b/apps/customer-service/src/components/tickets/ticket-list.tsx @@ -5,23 +5,23 @@ import { useParams, useRouter, useSearchParams } from 'next/navigation'; import { PartyPopper } from 'lucide-react'; import { FormattedMessage } from 'react-intl'; +import { TicketStatus } from '@cs/database/schema/ticket'; + import { TicketListItem } from '~/components/tickets/ticket-list-item'; import { api } from '~/utils/api'; -export const TicketList: FC = () => { +export const TicketList: FC<{ + filter: 'all' | 'me' | 'unassigned'; + status: TicketStatus; + orderBy: 'newest' | 'oldest'; +}> = ({ filter, status, orderBy }) => { const params = useParams(); const router = useRouter(); const searchParams = useSearchParams(); - const filter = - searchParams.get('filter') === 'me' - ? 'me' - : searchParams.get('filter') === 'unassigned' - ? 'unassigned' - : 'all'; - const orderBy = - searchParams.get('orderBy') === 'oldest' ? 'oldest' : 'newest'; + const [ticketsData] = api.ticket.all.useSuspenseQuery({ filter: filter, + status: status, orderBy: orderBy, }); diff --git a/apps/customer-service/src/utils/search-params.ts b/apps/customer-service/src/utils/search-params.ts new file mode 100644 index 000000000..2c950a4c8 --- /dev/null +++ b/apps/customer-service/src/utils/search-params.ts @@ -0,0 +1,21 @@ +import { ReadonlyURLSearchParams } from 'next/navigation'; + +export const FILTER_QUERY_PARAM = 'filter'; +export const STATUS_QUERY_PARAM = 'status'; +export const ORDER_BY_QUERY_PARAM = 'orderBy'; + +export const getUpdatedSearchParams = ( + searchParams: ReadonlyURLSearchParams, + key: string, + value: string +) => { + const updatedSeachParams = new URLSearchParams( + Array.from(searchParams.entries()) + ); + + if (updatedSeachParams.get(key) !== value) { + updatedSeachParams.set(key, value); + } + + return updatedSeachParams; +}; diff --git a/packages/api/src/router/ticket.ts b/packages/api/src/router/ticket.ts index 5531d740f..26dc34c0f 100644 --- a/packages/api/src/router/ticket.ts +++ b/packages/api/src/router/ticket.ts @@ -17,6 +17,7 @@ export const ticketRouter = createTRPCRouter({ .input( z.object({ filter: z.enum(['all', 'me', 'unassigned']), + status: z.enum([TicketStatus.Open, TicketStatus.Resolved]), orderBy: z.enum(['newest', 'oldest']), }) ) @@ -26,11 +27,17 @@ export const ticketRouter = createTRPCRouter({ newest: desc(schema.tickets.createdAt), oldest: asc(schema.tickets.createdAt), }[input.orderBy], - where: { - all: undefined, - me: eq(schema.tickets.assignedToId, ctx.session.user.contactId ?? 0), - unassigned: isNull(schema.tickets.assignedToId), - }[input.filter], + where: and( + eq(schema.tickets.status, input.status), + { + all: undefined, + me: eq( + schema.tickets.assignedToId, + ctx.session.user.contactId ?? 0 + ), + unassigned: isNull(schema.tickets.assignedToId), + }[input.filter] + ), with: { author: true }, }); }),