-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1769 from gettakaro/feature/roles-table-view
Feat: add table view for roles
- Loading branch information
Showing
6 changed files
with
394 additions
and
174 deletions.
There are no files selected for viewing
27 changes: 27 additions & 0 deletions
27
packages/web-main/src/components/TableListToggleButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { ToggleButtonGroup } from '@takaro/lib-components'; | ||
import { FC } from 'react'; | ||
import { AiOutlineTable as TableViewIcon, AiOutlineUnorderedList as ListViewIcon } from 'react-icons/ai'; | ||
|
||
export type ViewType = 'list' | 'table'; | ||
interface TableListToggleButtonProps { | ||
onChange: (view: ViewType) => void; | ||
value: ViewType; | ||
} | ||
|
||
export const TableListToggleButton: FC<TableListToggleButtonProps> = ({ value, onChange }) => { | ||
return ( | ||
<ToggleButtonGroup | ||
onChange={(val) => onChange(val as ViewType)} | ||
exclusive={true} | ||
orientation="horizontal" | ||
defaultValue={value} | ||
> | ||
<ToggleButtonGroup.Button value="list" tooltip="List view"> | ||
<ListViewIcon size={20} /> | ||
</ToggleButtonGroup.Button> | ||
<ToggleButtonGroup.Button value="table" tooltip="Table view"> | ||
<TableViewIcon size={20} /> | ||
</ToggleButtonGroup.Button> | ||
</ToggleButtonGroup> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
53 changes: 53 additions & 0 deletions
53
packages/web-main/src/routes/_auth/_global/-roles/RolesCardView.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
import { AddCard, CardList, RoleCard } from 'components/cards'; | ||
import { useNavigate } from '@tanstack/react-router'; | ||
import { InfiniteScroll, Skeleton } from '@takaro/lib-components'; | ||
import { rolesInfiniteQueryOptions } from 'queries/role'; | ||
import { useInfiniteQuery } from '@tanstack/react-query'; | ||
|
||
export const RolesCardView = () => { | ||
const { | ||
data: roles, | ||
isLoading, | ||
isFetching, | ||
hasNextPage, | ||
fetchNextPage, | ||
isFetchingNextPage, | ||
} = useInfiniteQuery({ | ||
...rolesInfiniteQueryOptions({ sortBy: 'system', sortDirection: 'desc' }), | ||
}); | ||
const navigate = useNavigate(); | ||
|
||
if (isLoading) { | ||
return ( | ||
<CardList> | ||
<Skeleton variant="rectangular" height="100%" width="100%" /> | ||
<Skeleton variant="rectangular" height="100%" width="100%" /> | ||
<Skeleton variant="rectangular" height="100%" width="100%" /> | ||
<Skeleton variant="rectangular" height="100%" width="100%" /> | ||
</CardList> | ||
); | ||
} | ||
|
||
if (!roles) { | ||
return 'Failed to load roles?'; | ||
} | ||
|
||
return ( | ||
<> | ||
<CardList> | ||
{roles.pages | ||
.flatMap((page) => page.data) | ||
.map((role) => ( | ||
<RoleCard key={role.id} {...role} /> | ||
))} | ||
<AddCard title="Role" onClick={() => navigate({ to: '/roles/create' })} /> | ||
</CardList> | ||
<InfiniteScroll | ||
isFetching={isFetching} | ||
hasNextPage={hasNextPage} | ||
fetchNextPage={fetchNextPage} | ||
isFetchingNextPage={isFetchingNextPage} | ||
/> | ||
</> | ||
); | ||
}; |
168 changes: 168 additions & 0 deletions
168
packages/web-main/src/routes/_auth/_global/-roles/RolesTableView.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
import { RoleOutputDTO, RoleSearchInputDTOSortDirectionEnum } from '@takaro/apiclient'; | ||
import { Chip, Skeleton, Table, Tooltip, useTableActions } from '@takaro/lib-components'; | ||
import { useQuery } from '@tanstack/react-query'; | ||
import { rolesQueryOptions } from 'queries/role'; | ||
import { FC, useState } from 'react'; | ||
import { createColumnHelper } from '@tanstack/react-table'; | ||
import { RoleActions } from '../roles'; | ||
import { Link } from '@tanstack/react-router'; | ||
import { playersQueryOptions } from 'queries/player'; | ||
import { usersQueryOptions } from 'queries/user'; | ||
|
||
export const RolesTableView = () => { | ||
const { pagination, columnFilters, sorting, columnSearch } = useTableActions<RoleOutputDTO>(); | ||
const [quickSearchInput, setQuickSearchInput] = useState<string>(''); | ||
|
||
const { data, isLoading } = useQuery({ | ||
...rolesQueryOptions({ | ||
page: pagination.paginationState.pageIndex, | ||
limit: pagination.paginationState.pageSize, | ||
sortBy: sorting.sortingState[0]?.id, | ||
sortDirection: sorting.sortingState[0] | ||
? sorting.sortingState[0]?.desc | ||
? RoleSearchInputDTOSortDirectionEnum.Desc | ||
: RoleSearchInputDTOSortDirectionEnum.Asc | ||
: undefined, | ||
filters: { | ||
id: columnFilters.columnFiltersState.find((filter) => filter.id === 'id')?.value, | ||
name: columnFilters.columnFiltersState.find((filter) => filter.id === 'name')?.value, | ||
}, | ||
search: { | ||
id: columnSearch.columnSearchState.find((search) => search.id === 'id')?.value, | ||
name: [ | ||
...(columnSearch.columnSearchState.find((search) => search.id === 'name')?.value ?? []), | ||
quickSearchInput, | ||
], | ||
}, | ||
}), | ||
}); | ||
|
||
const columnHelper = createColumnHelper<RoleOutputDTO>(); | ||
const columnDefs = [ | ||
columnHelper.accessor('id', { | ||
id: 'id', | ||
header: 'Id', | ||
cell: (info) => info.getValue(), | ||
enableColumnFilter: true, | ||
meta: { | ||
hideColumn: true, | ||
}, | ||
}), | ||
columnHelper.accessor('name', { | ||
id: 'name', | ||
header: 'Name', | ||
cell: (info) => ( | ||
<Tooltip placement="right"> | ||
<Tooltip.Trigger asChild> | ||
<Link className="underline" to="/roles/view/$roleId" params={{ roleId: info.row.original.id }}> | ||
{info.getValue()} | ||
</Link> | ||
</Tooltip.Trigger> | ||
<Tooltip.Content>View role</Tooltip.Content> | ||
</Tooltip> | ||
), | ||
}), | ||
|
||
columnHelper.accessor('system', { | ||
id: 'system', | ||
header: 'System', | ||
cell: (info) => | ||
info.getValue() ? ( | ||
<Tooltip> | ||
<Tooltip.Trigger> | ||
<Chip variant="outline" color="primary" label="System" /> | ||
</Tooltip.Trigger> | ||
<Tooltip.Content>System roles are managed by Takaro and cannot be deleted.</Tooltip.Content> | ||
</Tooltip> | ||
) : ( | ||
<Chip variant="outline" color="backgroundAccent" label="Custom" /> | ||
), | ||
enableColumnFilter: false, | ||
enableGlobalFilter: false, | ||
}), | ||
columnHelper.accessor('id', { | ||
id: 'playerCount', | ||
header: 'Players with this role', | ||
cell: (info) => { | ||
return <PlayerCount roleId={info.getValue()} />; | ||
}, | ||
}), | ||
columnHelper.accessor('id', { | ||
id: 'userCount', | ||
header: 'Users with this role', | ||
cell: (info) => { | ||
return <UserCount roleId={info.getValue()} />; | ||
}, | ||
}), | ||
columnHelper.display({ | ||
id: 'actions', | ||
header: 'Actions', | ||
maxSize: 25, | ||
cell: (info) => { | ||
return ( | ||
<RoleActions | ||
roleId={info.row.original.id} | ||
roleName={info.row.original.name} | ||
isSystem={info.row.original.system} | ||
/> | ||
); | ||
}, | ||
}), | ||
]; | ||
|
||
const p = | ||
!isLoading && data | ||
? { | ||
paginationState: pagination.paginationState, | ||
setPaginationState: pagination.setPaginationState, | ||
pageOptions: pagination.getPageOptions(data), | ||
} | ||
: undefined; | ||
|
||
return ( | ||
<Table | ||
title="List of roles" | ||
searchInputPlaceholder="Search by role name..." | ||
onSearchInputChanged={setQuickSearchInput} | ||
id="roles" | ||
columns={columnDefs} | ||
data={data?.data ?? []} | ||
columnFiltering={columnFilters} | ||
columnSearch={columnSearch} | ||
sorting={sorting} | ||
pagination={p} | ||
/> | ||
); | ||
}; | ||
|
||
const PlayerCount: FC<{ roleId: string }> = ({ roleId }) => { | ||
const { data, isPending } = useQuery({ | ||
...playersQueryOptions({ filters: { roleId: [roleId] } }), | ||
}); | ||
|
||
if (isPending) { | ||
return <Skeleton variant="text" width="50px" height="15px" />; | ||
} | ||
|
||
if (!data) { | ||
return 'unknown'; | ||
} | ||
|
||
return data.data.length; | ||
}; | ||
|
||
const UserCount: FC<{ roleId: string }> = ({ roleId }) => { | ||
const { data, isPending } = useQuery({ | ||
...usersQueryOptions({ filters: { roleId: [roleId] } }), | ||
}); | ||
|
||
if (isPending) { | ||
return <Skeleton variant="text" width="50px" height="15px" />; | ||
} | ||
|
||
if (!data) { | ||
return 'unknown'; | ||
} | ||
|
||
return data.data.length; | ||
}; |
Oops, something went wrong.