Skip to content
This repository has been archived by the owner on Nov 4, 2024. It is now read-only.

Commit

Permalink
feat(ticket-list): filter by status
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidRouyer committed Sep 19, 2023
1 parent 73bde3e commit 1e28054
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 69 deletions.
7 changes: 4 additions & 3 deletions apps/customer-service/src/app/tickets/layout-with-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 [
Expand All @@ -34,17 +35,17 @@ const useNavigationLinks = () => {
<AccordionContent>
<ul className="flex flex-col gap-y-1">
<li>
<Link href="/tickets?filter=me">
<Link href={`/tickets?${FILTER_QUERY_PARAM}=me`}>
<FormattedMessage id="layout.tickets.my_tickets" />
</Link>
</li>
<li>
<Link href="/tickets?filter=all">
<Link href={`/tickets?${FILTER_QUERY_PARAM}=all`}>
<FormattedMessage id="layout.tickets.all_tickets" />
</Link>
</li>
<li>
<Link href="/tickets?filter=unassigned">
<Link href={`/tickets?${FILTER_QUERY_PARAM}=unassigned`}>
<FormattedMessage id="layout.tickets.unassigned_tickets" />
</Link>
</li>
Expand Down
21 changes: 2 additions & 19 deletions apps/customer-service/src/app/tickets/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { Suspense } from 'react';

import { LayoutWithSidebar } from '~/app/tickets/layout-with-sidebar';
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 { TicketListContainer } from '~/components/tickets/ticket-list-container';

export default function TicketsLayout({
children,
Expand All @@ -15,20 +11,7 @@ export default function TicketsLayout({
<LayoutWithSidebar>
{children}

<aside className="fixed inset-y-0 left-60 hidden w-96 flex-col border-r xl:flex">
<TicketListHeader />
<Suspense
fallback={
<div className="flex w-full flex-col gap-4">
<TicketListItemSkeleton />
<TicketListItemSkeleton />
<TicketListItemSkeleton />
</div>
}
>
<TicketList />
</Suspense>
</aside>
<TicketListContainer />
</LayoutWithSidebar>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Link from 'next/link';
import { useSession } from 'next-auth/react';

import { api } from '~/utils/api';
import { FILTER_QUERY_PARAM } from '~/utils/search-params';

export const TeamMemberList: FC = () => {
const session = useSession();
Expand All @@ -14,7 +15,9 @@ export const TeamMemberList: FC = () => {
?.filter((contact) => contact.id !== session.data?.user?.contactId)
?.map((contact) => (
<li key={contact.id}>
<Link href={`/tickets?filter=${contact.id}`}>{contact.name}</Link>
<Link href={`/tickets?${FILTER_QUERY_PARAM}=${contact.id}`}>
{contact.name}
</Link>
</li>
))}
</ul>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<aside className="fixed inset-y-0 left-60 hidden w-96 flex-col border-r xl:flex">
<TicketListHeader status={status} orderBy={orderBy} />
<Suspense
fallback={
<div className="flex w-full flex-col gap-4">
<TicketListItemSkeleton />
<TicketListItemSkeleton />
<TicketListItemSkeleton />
</div>
}
>
<TicketList filter={filter} status={status} orderBy={orderBy} />
</Suspense>
</aside>
);
};
101 changes: 69 additions & 32 deletions apps/customer-service/src/components/tickets/ticket-list-header.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 (
<header className="flex items-center justify-between border-b px-4 py-6 sm:px-6">
<h1 className="text-base font-semibold leading-7 text-white">Inbox</h1>
Expand All @@ -54,7 +39,59 @@ export const TicketListHeader: FC = () => {
variant="secondary"
className="flex items-center justify-between gap-x-1 text-sm leading-6"
>
<FormattedMessage id="ticket.sort_by.newest" />
{
{
Resolved: <FormattedMessage id="ticket.statuses.resolved" />,
Open: <FormattedMessage id="ticket.statuses.open" />,
}[status]
}
<ChevronDown className="h-5 w-5" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuGroup>
<DropdownMenuItem
onClick={() =>
router.replace(
`${pathname}?${getUpdatedSearchParams(
searchParams,
STATUS_QUERY_PARAM,
'open'
).toString()}`
)
}
>
<FormattedMessage id="ticket.statuses.open" />
</DropdownMenuItem>
<DropdownMenuItem
onClick={() =>
router.replace(
`${pathname}?${getUpdatedSearchParams(
searchParams,
STATUS_QUERY_PARAM,
'resolved'
).toString()}`
)
}
>
<FormattedMessage id="ticket.statuses.resolved" />
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
type="button"
variant="secondary"
className="flex items-center justify-between gap-x-1 text-sm leading-6"
>
{
{
newest: <FormattedMessage id="ticket.sort_by.newest" />,
oldest: <FormattedMessage id="ticket.sort_by.oldest" />,
}[orderBy]
}
<ChevronDown className="h-5 w-5" />
</Button>
</DropdownMenuTrigger>
Expand All @@ -65,9 +102,9 @@ export const TicketListHeader: FC = () => {
router.replace(
`${pathname}?${getUpdatedSearchParams(
searchParams,
'oldest',
ORDER_BY_QUERY_PARAM,
'newest'
)}`
).toString()}`
)
}
>
Expand All @@ -78,9 +115,9 @@ export const TicketListHeader: FC = () => {
router.replace(
`${pathname}?${getUpdatedSearchParams(
searchParams,
'newest',
ORDER_BY_QUERY_PARAM,
'oldest'
)}`
).toString()}`
)
}
>
Expand Down
18 changes: 9 additions & 9 deletions apps/customer-service/src/components/tickets/ticket-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});

Expand Down
21 changes: 21 additions & 0 deletions apps/customer-service/src/utils/search-params.ts
Original file line number Diff line number Diff line change
@@ -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;
};
17 changes: 12 additions & 5 deletions packages/api/src/router/ticket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']),
})
)
Expand All @@ -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 },
});
}),
Expand Down

0 comments on commit 1e28054

Please sign in to comment.