mirror of
https://github.com/documenso/documenso.git
synced 2026-07-24 08:54:20 +10:00
feat: add faceted table toolbars and multi-value filters
Consolidating document and template filtering into shared faceted toolbars makes filtering easier to discover and use. Supporting comma-separated query params enables multi-select filters across UI, server queries, and E2E coverage.
This commit is contained in:
@@ -11,14 +11,14 @@ import { type FindResultResponse } from '../../types/search-params';
|
||||
import { maskRecipientTokensForDocument } from '../../utils/mask-recipient-tokens-for-document';
|
||||
import { getTeamById } from '../team/get-team';
|
||||
|
||||
export type PeriodSelectorValue = '' | '7d' | '14d' | '30d';
|
||||
export type PeriodSelectorValue = '' | 'all' | '7d' | '14d' | '30d';
|
||||
|
||||
export type FindDocumentsOptions = {
|
||||
userId: number;
|
||||
teamId?: number;
|
||||
templateId?: number;
|
||||
source?: DocumentSource;
|
||||
status?: ExtendedDocumentStatus;
|
||||
source?: DocumentSource | DocumentSource[];
|
||||
status?: ExtendedDocumentStatus | ExtendedDocumentStatus[];
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
orderBy?: {
|
||||
@@ -36,7 +36,7 @@ export const findDocuments = async ({
|
||||
teamId,
|
||||
templateId,
|
||||
source,
|
||||
status = ExtendedDocumentStatus.ALL,
|
||||
status,
|
||||
page = 1,
|
||||
perPage = 10,
|
||||
orderBy,
|
||||
@@ -69,6 +69,8 @@ export const findDocuments = async ({
|
||||
const orderByDirection = orderBy?.direction ?? 'desc';
|
||||
const teamMemberRole = team?.currentTeamRole ?? null;
|
||||
|
||||
const normalizedStatuses = normalizeStatuses(status);
|
||||
|
||||
const searchFilter: Prisma.EnvelopeWhereInput = {
|
||||
OR: [
|
||||
{ title: { contains: query, mode: 'insensitive' } },
|
||||
@@ -111,10 +113,16 @@ export const findDocuments = async ({
|
||||
},
|
||||
];
|
||||
|
||||
let filters: Prisma.EnvelopeWhereInput | null = findDocumentsFilter(status, user, folderId);
|
||||
let filters: Prisma.EnvelopeWhereInput | null = mergeStatusFilters(
|
||||
normalizedStatuses.map((currentStatus) => findDocumentsFilter(currentStatus, user, folderId)),
|
||||
);
|
||||
|
||||
if (team) {
|
||||
filters = findTeamDocumentsFilter(status, team, visibilityFilters, folderId);
|
||||
filters = mergeStatusFilters(
|
||||
normalizedStatuses.map((currentStatus) =>
|
||||
findTeamDocumentsFilter(currentStatus, team, visibilityFilters, folderId),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (filters === null) {
|
||||
@@ -193,8 +201,12 @@ export const findDocuments = async ({
|
||||
}
|
||||
|
||||
if (source) {
|
||||
const sources = Array.isArray(source) ? source : [source];
|
||||
|
||||
whereAndClause.push({
|
||||
source,
|
||||
source: {
|
||||
in: sources,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -203,7 +215,7 @@ export const findDocuments = async ({
|
||||
AND: whereAndClause,
|
||||
};
|
||||
|
||||
if (period) {
|
||||
if (period && period !== 'all') {
|
||||
const daysAgo = parseInt(period.replace(/d$/, ''), 10);
|
||||
|
||||
const startOfPeriod = DateTime.now().minus({ days: daysAgo }).startOf('day');
|
||||
@@ -219,11 +231,7 @@ export const findDocuments = async ({
|
||||
};
|
||||
}
|
||||
|
||||
if (folderId !== undefined) {
|
||||
whereClause.folderId = folderId;
|
||||
} else {
|
||||
whereClause.folderId = null;
|
||||
}
|
||||
whereClause.folderId = folderId ?? null;
|
||||
|
||||
const [data, count] = await Promise.all([
|
||||
prisma.envelope.findMany({
|
||||
@@ -279,6 +287,39 @@ export const findDocuments = async ({
|
||||
} satisfies FindResultResponse<typeof data>;
|
||||
};
|
||||
|
||||
const normalizeStatuses = (status: FindDocumentsOptions['status']) => {
|
||||
if (!status) {
|
||||
return [ExtendedDocumentStatus.ALL];
|
||||
}
|
||||
|
||||
const statuses = Array.isArray(status) ? status : [status];
|
||||
const dedupedStatuses = Array.from(new Set(statuses));
|
||||
|
||||
if (dedupedStatuses.includes(ExtendedDocumentStatus.ALL)) {
|
||||
return [ExtendedDocumentStatus.ALL];
|
||||
}
|
||||
|
||||
return dedupedStatuses;
|
||||
};
|
||||
|
||||
const mergeStatusFilters = (filters: Array<Prisma.EnvelopeWhereInput | null>) => {
|
||||
const validFilters = filters.filter(
|
||||
(filter): filter is Prisma.EnvelopeWhereInput => filter !== null,
|
||||
);
|
||||
|
||||
if (validFilters.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (validFilters.length === 1) {
|
||||
return validFilters[0];
|
||||
}
|
||||
|
||||
return {
|
||||
OR: validFilters,
|
||||
} satisfies Prisma.EnvelopeWhereInput;
|
||||
};
|
||||
|
||||
const findDocumentsFilter = (
|
||||
status: ExtendedDocumentStatus,
|
||||
user: Pick<User, 'id' | 'email' | 'name'>,
|
||||
|
||||
@@ -25,7 +25,7 @@ export const getStats = async ({
|
||||
}: GetStatsInput) => {
|
||||
let createdAt: Prisma.EnvelopeWhereInput['createdAt'];
|
||||
|
||||
if (period) {
|
||||
if (period && period !== 'all') {
|
||||
const daysAgo = parseInt(period.replace(/d$/, ''), 10);
|
||||
|
||||
const startOfPeriod = DateTime.now().minus({ days: daysAgo }).startOf('day');
|
||||
|
||||
Reference in New Issue
Block a user