Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FS-236]: Add Pagination To The Household List Table #128

Merged
merged 18 commits into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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