Skip to content

Commit

Permalink
[FS-236]: Add Pagination To The Household List Table (#128)
Browse files Browse the repository at this point in the history
This PR adds pagination to the household list table.

---------

Co-authored-by: Alireza Safaierad <frontendmonster@gmail.com>
  • Loading branch information
AmirabbasJ and ASafaeirad authored Sep 19, 2023
1 parent 8dc4333 commit c58110e
Show file tree
Hide file tree
Showing 24 changed files with 937 additions and 432 deletions.
42 changes: 24 additions & 18 deletions app/Dashboard/Households/HouseholdList/HouseholdList.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import type { HouseholdListDto } from '@camp/data-layer';
import { useHouseholdListQuery } from '@camp/data-layer';
import {
DashboardCard,
DashboardTitle,
FullPageLoader,
showNotification,
} from '@camp/design';
import { DashboardCard, DashboardTitle, showNotification } from '@camp/design';
import { householdColumnHelper } from '@camp/domain';
import { errorMessages, messages } from '@camp/messages';
import { AppRoute } from '@camp/router';
import { tid } from '@camp/test';
import { isEmpty, isNull } from '@fullstacksjs/toolbox';
import { isEmpty } from '@fullstacksjs/toolbox';
import { Group } from '@mantine/core';
import type { SortingState } from '@tanstack/react-table';
import type { PaginationState, SortingState } from '@tanstack/react-table';
import { getCoreRowModel, useReactTable } from '@tanstack/react-table';
import { useState } from 'react';

Expand Down Expand Up @@ -61,20 +57,32 @@ const columns = [
}),
];

const empty: any[] = [];
const empty: HouseholdListDto['household'] = [];

export const HouseholdList = () => {
const [sorting, setSorting] = useState<SortingState>([]);
const [pagination, setPagination] = useState<PaginationState>({
pageIndex: 0,
pageSize: 10,
});

const { data, loading, error } = useHouseholdListQuery({
variables: { orderBy: sorting },
const { data, loading, error, previousData } = useHouseholdListQuery({
variables: {
orderBy: sorting,
range: pagination,
},
});
const households = data?.household ?? null;

const householdsCount = data?.totalCount ?? previousData?.totalCount ?? 0;
const households = data?.household ?? empty;

const table = useReactTable({
data: households ?? empty,
data: households,
columns,
state: { sorting },
state: { sorting, pagination },
onPaginationChange: setPagination,
manualPagination: true,
pageCount: Math.ceil(householdsCount / pagination.pageSize),
onSortingChange: setSorting,
getCoreRowModel: getCoreRowModel(),
});
Expand All @@ -89,16 +97,14 @@ export const HouseholdList = () => {
return null;
}

if (loading) return <FullPageLoader />;
if (isNull(households)) return null;
if (isEmpty(households)) return <HouseholdEmptyState />;
if (isEmpty(households) && !loading) return <HouseholdEmptyState />;

return (
<DashboardCard
left={<CreateHouseholdButton />}
right={<DashboardTitle>{t.title}</DashboardTitle>}
>
<HouseholdTable table={table} />
<HouseholdTable loading={loading} table={table} />
</DashboardCard>
);
};
73 changes: 12 additions & 61 deletions app/Dashboard/Households/HouseholdList/HouseholdTable.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,18 @@
import { Table } from '@camp/design';
import { DataTable } from '@camp/design';
import type { HouseholdKeys, HouseholdListItem } from '@camp/domain';
import type { Icon } from '@camp/icons';
import { ArrowDownIcon, ArrowUpIcon } from '@camp/icons';
import type { AppRoute } from '@camp/router';
import { useNavigate } from '@camp/router';
import { Text } from '@mantine/core';
import type { Table as TableType } from '@tanstack/react-table';
import { flexRender } from '@tanstack/react-table';
import { useCallback } from 'react';

import * as ids from './HouseholdList.ids';

interface Props {
loading: boolean;
table: TableType<HouseholdKeys & HouseholdListItem>;
}

const iconMap: Record<string, Icon> = {
asc: ArrowUpIcon,
desc: ArrowDownIcon,
} as const;

export const HouseholdTable = ({ table }: Props) => {
export const HouseholdTable = ({ loading, table }: Props) => {
const navigate = useNavigate();
const gotoDetail = useCallback(
(householdId: string) => {
Expand All @@ -30,55 +22,14 @@ export const HouseholdTable = ({ table }: Props) => {
);

return (
<Table id={ids.householdTableId}>
<thead>
{table.getHeaderGroups().map(headerGroup => (
<tr key={headerGroup.id}>
{headerGroup.headers.map(header => {
const Icon = iconMap[header.column.getIsSorted() as string];
const canSort = header.column.getCanSort();

return (
<th key={header.id} colSpan={header.colSpan}>
{header.isPlaceholder ? null : (
<Text
sx={{
cursor: canSort ? 'pointer' : undefined,
display: 'flex',
alignItems: 'center',
userSelect: canSort ? 'none' : undefined,
gap: 5,
}}
onClick={header.column.getToggleSortingHandler()}
>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
{Icon ? <Icon size={14} /> : null}
</Text>
)}
</th>
);
})}
</tr>
))}
</thead>
<tbody>
{table.getRowModel().rows.map(row => (
<tr
style={{ cursor: 'pointer' }}
key={row.id}
onClick={() => gotoDetail(row.original.id)}
>
{row.getVisibleCells().map(cell => (
<td key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</td>
))}
</tr>
))}
</tbody>
</Table>
<DataTable<HouseholdKeys & HouseholdListItem>
id={ids.householdTableId}
table={table}
loading={loading}
// TODO add skeleton here
// eslint-disable-next-line react/jsx-no-useless-fragment
fallback={<></>}
onRowClick={({ id }: HouseholdKeys & HouseholdListItem) => gotoDetail(id)}
/>
);
};
6 changes: 6 additions & 0 deletions app/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ export const errorMessages: Record<ServerError, string> = {
};

export const messages = {
tablePagination: {
page: (currentPage: number, total: number) =>
`صفحه ${currentPage} از ${Math.max(total, 1)}`,
prevPage: 'صفحه قبل',
nextPage: 'صفحه بعد',
},
notification: {
household: {
delete: {
Expand Down
6 changes: 5 additions & 1 deletion libs/api-client/useQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,15 @@ export const useQuery = <
});

const data = useMemo(() => mapper(result.data), [result.data, mapper]);

const previousData = useMemo(
() => mapper(result.previousData),
[result.previousData, mapper],
);
// NOTE: we need to implement other functions that rely on Data type ourself
return {
...result,
data,
previousData,
refetch: async (variables: ReturnType<TVariableMapper>) => {
const res = await result.refetch(options.mapVariables(variables));
return { ...res, data: mapper(res.data) };
Expand Down
4 changes: 3 additions & 1 deletion libs/data-layer/ApiOperations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,12 @@ export type ApiUpsertMemberMutation = { __typename?: 'mutation_root', insert_mem

export type ApiHouseholdListQueryVariables = SchemaTypes.Exact<{
order_by?: SchemaTypes.InputMaybe<Array<SchemaTypes.ApiHouseholdOrderBy> | SchemaTypes.ApiHouseholdOrderBy>;
limit?: SchemaTypes.InputMaybe<SchemaTypes.Scalars['Int']['input']>;
offset?: SchemaTypes.InputMaybe<SchemaTypes.Scalars['Int']['input']>;
}>;


export type ApiHouseholdListQuery = { __typename?: 'query_root', household: Array<{ __typename?: 'household', id: string, name: string, severity: SchemaTypes.HouseholdSeverityEnum, status: SchemaTypes.HouseholdStatusEnum }> };
export type ApiHouseholdListQuery = { __typename?: 'query_root', household: Array<{ __typename?: 'household', id: string, name: string, severity: SchemaTypes.HouseholdSeverityEnum, status: SchemaTypes.HouseholdStatusEnum }>, household_aggregate: { __typename?: 'household_aggregate', aggregate?: { __typename?: 'household_aggregate_fields', count: number } | null } };

export type ApiHouseholdQueryVariables = SchemaTypes.Exact<{
id: SchemaTypes.Scalars['uuid']['input'];
Expand Down
20 changes: 14 additions & 6 deletions libs/data-layer/operations/mutations/useCompleteHousehold.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,26 @@ export function useCompleteHouseholdMutation(
const newHouseholds = result.data?.update_household_by_pk;
if (!newHouseholds) return;

const prevHouseholdsQuery = cache.readQuery<ApiHouseholdListQuery>({
const prevHouseholdListQuery = cache.readQuery<ApiHouseholdListQuery>({
query: HouseholdListDocument,
});

const newHousehold = [
...(prevHouseholdsQuery?.household ?? []),
newHouseholds,
];
const prevHouseholds = prevHouseholdListQuery?.household ?? [];

cache.writeQuery<ApiHouseholdListQuery>({
query: HouseholdListDocument,
data: { household: newHousehold },
data: {
household_aggregate: {
aggregate: {
count:
prevHouseholdListQuery?.household_aggregate.aggregate?.count ??
0,
},
},
household: prevHouseholds.map(h =>
h.id === newHouseholds.id ? newHouseholds : h,
),
},
});

return options?.update?.(cache, result, opts);
Expand Down
10 changes: 10 additions & 0 deletions libs/data-layer/operations/mutations/useCreateHouseholdMutation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { gql } from '@apollo/client';
import { mergeDeep } from '@apollo/client/utilities';
import type { MutationOptions } from '@camp/api-client';
import { useMutation } from '@camp/api-client';
import type {
Expand Down Expand Up @@ -80,6 +81,15 @@ export function useCreateHouseholdMutation(
});
return [newHouseholdRef!, ...existingHouseholdsRefs];
},
household_aggregate(existingAggregate) {
return mergeDeep(existingAggregate, {
aggregate: {
count: existingAggregate.aggregate?.count
? existingAggregate.aggregate.count + 1
: undefined,
},
});
},
},
});

Expand Down
17 changes: 17 additions & 0 deletions libs/data-layer/operations/mutations/useDeleteHouseholdMutation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { gql } from '@apollo/client';
import { mergeDeep } from '@apollo/client/utilities';
import type { MutationOptions } from '@camp/api-client';
import { useMutation } from '@camp/api-client';
import type {
Expand Down Expand Up @@ -57,6 +58,22 @@ export const useDeleteHouseholdMutation = (

cache.evict({ id: cache.identify(household) });
cache.gc();

cache.evict({ fieldName: 'household' });

cache.modify({
fields: {
household_aggregate(existingAggregate) {
return mergeDeep(existingAggregate, {
aggregate: {
count: existingAggregate.aggregate?.count
? existingAggregate.aggregate.count - 1
: undefined,
},
});
},
},
});
},
});
};
31 changes: 23 additions & 8 deletions libs/data-layer/operations/queries/useHouseholdListQuery.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { gql } from '@apollo/client';
import type { QueryHookOptions } from '@camp/api-client';
import { useQuery } from '@camp/api-client';
import type { Household } from '@camp/domain';
import type { HouseholdKeys, HouseholdListItem } from '@camp/domain';
import { ApiOrderBy } from '@camp/domain';
import { isEmpty } from '@fullstacksjs/toolbox';
import type { SortingState } from '@tanstack/react-table';
import type { Nullish } from '@fullstacksjs/toolbox';
import { isEmpty, isNotNull } from '@fullstacksjs/toolbox';
import type { PaginationState, SortingState } from '@tanstack/react-table';

import type {
ApiHouseholdListQuery,
Expand All @@ -18,32 +19,44 @@ import {
} from '../fragments';

export const HouseholdListDocument = gql`
query HouseholdList($order_by: [household_order_by!]) {
household(order_by: $order_by) {
query HouseholdList(
$order_by: [household_order_by!]
$limit: Int
$offset: Int
) {
household(order_by: $order_by, limit: $limit, offset: $offset) {
...HouseholdKeys
...HouseholdListItem
}
household_aggregate {
aggregate {
count
}
}
}
${HouseholdKeysFragment}
${HouseholdListItemFragment}
`;

export interface HouseholdListDto {
household: Household[];
household: (HouseholdKeys & HouseholdListItem)[];
totalCount: Nullish | number;
}

const toClient = (data: ApiHouseholdListQuery | null) => {
const toClient = (data: ApiHouseholdListQuery | null): HouseholdListDto => {
return {
household:
data?.household.filter(Boolean).map(d => ({
data?.household.filter(isNotNull).map(d => ({
...getHouseholdKeys(d),
...getHouseholdListItem(d),
})) ?? [],
totalCount: data?.household_aggregate.aggregate?.count,
};
};

interface Variables {
orderBy: SortingState;
range: PaginationState;
}

const toApiVariables = (data: Variables): ApiHouseholdListQueryVariables => {
Expand All @@ -56,6 +69,8 @@ const toApiVariables = (data: Variables): ApiHouseholdListQueryVariables => {
[item.id]: item.desc ? ApiOrderBy.Desc : ApiOrderBy.Asc,
};
}, {}),
limit: data.range.pageSize,
offset: data.range.pageSize * data.range.pageIndex,
};
};

Expand Down
Loading

0 comments on commit c58110e

Please sign in to comment.