diff --git a/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/profiles/profiles.tsx b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/profiles/profiles.tsx index 5c75be0a..a50da9c6 100644 --- a/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/profiles/profiles.tsx +++ b/apps/dashboard/src/app/(app)/[organizationSlug]/[projectId]/profiles/profiles.tsx @@ -1,6 +1,9 @@ 'use client'; +import { TableButtons } from '@/components/data-table'; import { ProfilesTable } from '@/components/profiles/table'; +import { Input } from '@/components/ui/input'; +import { useDebounceValue } from '@/hooks/useDebounceValue'; import { api } from '@/trpc/client'; import { parseAsInteger, useQueryState } from 'nuqs'; @@ -14,12 +17,17 @@ const Events = ({ projectId }: Props) => { 'cursor', parseAsInteger.withDefault(0) ); + const [search, setSearch] = useQueryState('search', { + defaultValue: '', + shallow: true, + }); + const debouncedSearch = useDebounceValue(search, 500); const query = api.profile.list.useQuery( { cursor, projectId, take: 50, - // filters, + search: debouncedSearch, }, { keepPreviousData: true, @@ -28,6 +36,13 @@ const Events = ({ projectId }: Props) => { return (
+ + setSearch(e.target.value)} + placeholder="Search profiles" + /> +
); diff --git a/apps/dashboard/src/components/profiles/table/index.tsx b/apps/dashboard/src/components/profiles/table/index.tsx index 6672c2a6..888b3ce3 100644 --- a/apps/dashboard/src/components/profiles/table/index.tsx +++ b/apps/dashboard/src/components/profiles/table/index.tsx @@ -1,3 +1,4 @@ +import { memo } from 'react'; import type { Dispatch, SetStateAction } from 'react'; import { DataTable } from '@/components/data-table'; import { FullPageEmptyState } from '@/components/full-page-empty-state'; @@ -5,6 +6,7 @@ import { Pagination } from '@/components/pagination'; import { Button } from '@/components/ui/button'; import { TableSkeleton } from '@/components/ui/table'; import type { UseQueryResult } from '@tanstack/react-query'; +import isEqual from 'lodash.isequal'; import { GanttChartIcon } from 'lucide-react'; import type { IServiceProfile } from '@openpanel/db'; @@ -22,44 +24,53 @@ type Props = setCursor: Dispatch>; }); -export const ProfilesTable = ({ type, query, ...props }: Props) => { - const columns = useColumns(type); - const { data, isFetching, isLoading } = query; +export const ProfilesTable = memo( + ({ type, query, ...props }: Props) => { + console.log('re-render'); - if (isLoading) { - return ; - } + const columns = useColumns(type); + const { data, isFetching, isLoading } = query; + + if (isLoading) { + return ; + } + + if (data?.length === 0) { + return ( + +

Could not find any profiles

+ {'cursor' in props && props.cursor !== 0 && ( + + )} +
+ ); + } - if (data?.length === 0) { return ( - -

Could not find any profiles

- {'cursor' in props && props.cursor !== 0 && ( - + <> + + {'cursor' in props && ( + )} -
+ ); + }, + (prevProps, nextProps) => { + return isEqual(prevProps.query.data, nextProps.query.data); } +); - return ( - <> - - {'cursor' in props && ( - - )} - - ); -}; +ProfilesTable.displayName = 'ProfilesTable'; diff --git a/packages/db/src/services/profile.service.ts b/packages/db/src/services/profile.service.ts index 9155b6bc..a54e3ff1 100644 --- a/packages/db/src/services/profile.service.ts +++ b/packages/db/src/services/profile.service.ts @@ -66,6 +66,7 @@ interface GetProfileListOptions { take: number; cursor?: number; filters?: IChartEventFilter[]; + search?: string; } export async function getProfiles(ids: string[]) { @@ -90,6 +91,7 @@ export async function getProfileList({ cursor, projectId, filters, + search, }: GetProfileListOptions) { const { sb, getSql } = createSqlBuilder(); sb.from = 'profiles FINAL'; @@ -98,6 +100,13 @@ export async function getProfileList({ sb.limit = take; sb.offset = Math.max(0, (cursor ?? 0) * take); sb.orderBy.created_at = 'created_at DESC'; + if (search) { + if (search.includes('@')) { + sb.where.email = `email ILIKE '%${search}%'`; + } else { + sb.where.first_name = `first_name ILIKE '%${search}%' OR last_name ILIKE '%${search}%'`; + } + } const data = await chQuery(getSql()); return data.map(transformProfile); } @@ -107,7 +116,7 @@ export async function getProfileListCount({ filters, }: Omit) { const { sb, getSql } = createSqlBuilder(); - sb.from = 'profiles FINAL'; + sb.from = 'profiles'; sb.select.count = 'count(id) as count'; sb.where.project_id = `project_id = ${escape(projectId)}`; sb.groupBy.project_id = 'project_id'; diff --git a/packages/trpc/src/routers/profile.ts b/packages/trpc/src/routers/profile.ts index 730064bd..af9307d8 100644 --- a/packages/trpc/src/routers/profile.ts +++ b/packages/trpc/src/routers/profile.ts @@ -40,11 +40,12 @@ export const profileRouter = createTRPCRouter({ projectId: z.string(), cursor: z.number().optional(), take: z.number().default(50), + search: z.string().optional(), // filters: z.array(zChartEventFilter).default([]), }) ) - .query(async ({ input: { projectId, cursor, take } }) => { - return getProfileList({ projectId, cursor, take }); + .query(async ({ input: { projectId, cursor, take, search } }) => { + return getProfileList({ projectId, cursor, take, search }); }), powerUsers: protectedProcedure