mirror of
https://github.com/documenso/documenso.git
synced 2026-08-23 14:52:23 +10:00
feat: replace template view tabs with a filter pill
This commit replaces the Team/Organisation tabs on the templates page with a FilterPill. The page uses the shared nuqs parsers for the view, page, and perPage URL parameters. The documents page (#3145) uses the same components. The e2e tests use the new filter test ID.
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Building2Icon } from 'lucide-react';
|
||||
import { useQueryStates } from 'nuqs';
|
||||
|
||||
import { FilterPill } from '~/components/general/filter-pill';
|
||||
import { TEMPLATES_VIEW_VALUES, templatesSearchParams } from '~/utils/templates-search-params';
|
||||
|
||||
const VIEW_OPTIONS = [
|
||||
{ value: 'team', label: <Trans>Team</Trans> },
|
||||
{ value: 'organisation', label: <Trans>Organisation</Trans> },
|
||||
];
|
||||
|
||||
export const TemplatesTableViewFilter = () => {
|
||||
const [{ view }, setSearchParams] = useQueryStates(
|
||||
{
|
||||
view: templatesSearchParams.view,
|
||||
page: templatesSearchParams.page,
|
||||
},
|
||||
{ history: 'push' },
|
||||
);
|
||||
|
||||
const onChange = (newView: string | null) => {
|
||||
void setSearchParams({
|
||||
view: TEMPLATES_VIEW_VALUES.find((value) => value === newView) ?? null,
|
||||
page: null,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<FilterPill
|
||||
icon={Building2Icon}
|
||||
label={<Trans>View</Trans>}
|
||||
value={view}
|
||||
onChange={onChange}
|
||||
options={VIEW_OPTIONS}
|
||||
testId="templates-table-view-filter"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -6,14 +6,13 @@ import { formatDocumentsPath, formatTemplatesPath } from '@documenso/lib/utils/t
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@documenso/ui/primitives/avatar';
|
||||
import type { RowSelectionState } from '@documenso/ui/primitives/data-table';
|
||||
import { Tabs, TabsList, TabsTrigger } from '@documenso/ui/primitives/tabs';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { EnvelopeType, OrganisationType } from '@prisma/client';
|
||||
import { Bird } from 'lucide-react';
|
||||
import { parseAsStringLiteral, useQueryState } from 'nuqs';
|
||||
import { useQueryStates } from 'nuqs';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useParams, useSearchParams } from 'react-router';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
import { EnvelopesBulkDeleteDialog } from '~/components/dialogs/envelopes-bulk-delete-dialog';
|
||||
import { EnvelopesBulkMoveDialog } from '~/components/dialogs/envelopes-bulk-move-dialog';
|
||||
@@ -21,10 +20,10 @@ import { EnvelopeDropZoneWrapper } from '~/components/general/envelope/envelope-
|
||||
import { FolderGrid } from '~/components/general/folder/folder-grid';
|
||||
import { EnvelopesTableBulkActionBar } from '~/components/tables/envelopes-table-bulk-action-bar';
|
||||
import { TemplatesTable } from '~/components/tables/templates-table';
|
||||
import { TemplatesTableViewFilter } from '~/components/tables/templates-table-view-filter';
|
||||
import { useCurrentTeam } from '~/providers/team';
|
||||
import { appMetaTags } from '~/utils/meta';
|
||||
|
||||
const TEMPLATE_VIEWS = ['team', 'organisation'] as const;
|
||||
import { templatesSearchParams } from '~/utils/templates-search-params';
|
||||
|
||||
export function meta() {
|
||||
return appMetaTags(msg`Templates`);
|
||||
@@ -39,15 +38,12 @@ export default function TemplatesPage() {
|
||||
const organisation = useCurrentOrganisation();
|
||||
|
||||
const { folderId } = useParams();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [findTemplateSearchParams] = useQueryStates(templatesSearchParams, {
|
||||
history: 'push',
|
||||
});
|
||||
|
||||
const page = Number(searchParams.get('page')) || 1;
|
||||
const perPage = Number(searchParams.get('perPage')) || 10;
|
||||
|
||||
const [view, setView] = useQueryState('view', parseAsStringLiteral(TEMPLATE_VIEWS).withDefault('team'));
|
||||
|
||||
const isOrgView = view === 'organisation';
|
||||
const showOrgTab = organisation.type !== OrganisationType.PERSONAL;
|
||||
const isOrgView = findTemplateSearchParams.view === 'organisation';
|
||||
const showOrgFilter = organisation.type !== OrganisationType.PERSONAL;
|
||||
|
||||
// Scoped by team so selections made in one team never leak into another.
|
||||
const [rowSelection, setRowSelection] = useSessionStorage<RowSelectionState>(
|
||||
@@ -66,8 +62,8 @@ export default function TemplatesPage() {
|
||||
|
||||
const teamTemplatesQuery = trpc.template.findTemplates.useQuery(
|
||||
{
|
||||
page,
|
||||
perPage,
|
||||
page: findTemplateSearchParams.page ?? undefined,
|
||||
perPage: findTemplateSearchParams.perPage ?? undefined,
|
||||
folderId,
|
||||
},
|
||||
{
|
||||
@@ -77,8 +73,8 @@ export default function TemplatesPage() {
|
||||
|
||||
const orgTemplatesQuery = trpc.template.findOrganisationTemplates.useQuery(
|
||||
{
|
||||
page,
|
||||
perPage,
|
||||
page: findTemplateSearchParams.page ?? undefined,
|
||||
perPage: findTemplateSearchParams.perPage ?? undefined,
|
||||
},
|
||||
{
|
||||
enabled: isOrgView,
|
||||
@@ -87,14 +83,6 @@ export default function TemplatesPage() {
|
||||
|
||||
const activeQuery = isOrgView ? orgTemplatesQuery : teamTemplatesQuery;
|
||||
|
||||
const handleViewChange = (newView: string) => {
|
||||
if (newView !== 'team' && newView !== 'organisation') {
|
||||
return;
|
||||
}
|
||||
|
||||
void setView(newView === 'team' ? null : newView);
|
||||
};
|
||||
|
||||
return (
|
||||
<EnvelopeDropZoneWrapper type={EnvelopeType.TEMPLATE}>
|
||||
<div className="mx-auto max-w-screen-xl px-4 md:px-8">
|
||||
@@ -112,26 +100,9 @@ export default function TemplatesPage() {
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{showOrgTab && (
|
||||
<div className="mt-6">
|
||||
<Tabs value={view} onValueChange={handleViewChange} data-testid="template-view-tabs">
|
||||
<TabsList>
|
||||
<TabsTrigger
|
||||
className="min-w-[60px] hover:text-foreground"
|
||||
value="team"
|
||||
data-testid="template-tab-team"
|
||||
>
|
||||
<Trans>Team</Trans>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
className="min-w-[60px] hover:text-foreground"
|
||||
value="organisation"
|
||||
data-testid="template-tab-organisation"
|
||||
>
|
||||
<Trans>Organisation</Trans>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
{showOrgFilter && (
|
||||
<div className="mt-6 flex flex-wrap items-center gap-x-2 gap-y-4">
|
||||
<TemplatesTableViewFilter />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { parseAsInteger, parseAsStringLiteral } from 'nuqs';
|
||||
|
||||
export const TEMPLATES_VIEW_VALUES = ['team', 'organisation'] as const;
|
||||
|
||||
/**
|
||||
* Shared nuqs parsers for the templates page URL state.
|
||||
*
|
||||
* Used by the templates page and its filter components so every consumer
|
||||
* parses and serialises the params identically.
|
||||
*/
|
||||
export const templatesSearchParams = {
|
||||
view: parseAsStringLiteral(TEMPLATES_VIEW_VALUES),
|
||||
page: parseAsInteger,
|
||||
perPage: parseAsInteger,
|
||||
};
|
||||
Reference in New Issue
Block a user