1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-08 23:35:31 +02:00

chore(deps): upgrade react-table to v8 [EE-4837] (#8245)

This commit is contained in:
Chaim Lev-Ari 2023-05-02 13:42:16 +07:00 committed by GitHub
parent f20d3e72b9
commit 757461d58b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
140 changed files with 1805 additions and 2872 deletions

View file

@ -1,5 +1,4 @@
import { Clock, Trash2 } from 'lucide-react';
import { useStore } from 'zustand';
import { notifySuccess } from '@/portainer/services/notifications';
import { withLimitToBE } from '@/react/hooks/useLimitToBE';
@ -9,7 +8,7 @@ import { Datatable } from '@@/datatables';
import { PageHeader } from '@@/PageHeader';
import { Button } from '@@/buttons';
import { Link } from '@@/Link';
import { useSearchBarState } from '@@/datatables/SearchBar';
import { useTableState } from '@@/datatables/useTableState';
import { useList } from '../queries/list';
import { EdgeUpdateSchedule, StatusType } from '../types';
@ -25,8 +24,7 @@ const settingsStore = createStore(storageKey);
export default withLimitToBE(ListView);
export function ListView() {
const settings = useStore(settingsStore);
const [search, setSearch] = useSearchBarState(storageKey);
const tableState = useTableState(settingsStore, storageKey);
const listQuery = useList(true);
@ -47,6 +45,7 @@ export function ListView() {
<Datatable
dataset={listQuery.data}
columns={columns}
settingsManager={tableState}
title="Update & rollback"
titleIcon={Clock}
emptyContentLabel="No schedules found"
@ -55,12 +54,6 @@ export function ListView() {
renderTableActions={(selectedRows) => (
<TableActions selectedRows={selectedRows} />
)}
initialPageSize={settings.pageSize}
onPageSizeChange={settings.setPageSize}
initialSortBy={settings.sortBy}
onSortByChange={settings.setSortBy}
searchValue={search}
onSearchChange={setSearch}
isRowSelectable={(row) => row.original.status === StatusType.Pending}
/>
</>

View file

@ -1,13 +1,12 @@
import { Column } from 'react-table';
import { isoDateFromTimestamp } from '@/portainer/filters/filters';
import { EdgeUpdateListItemResponse } from '../../queries/list';
import { columnHelper } from './helper';
export const created: Column<EdgeUpdateListItemResponse> = {
Header: 'Created',
accessor: (row) => isoDateFromTimestamp(row.created),
disableFilters: true,
Filter: () => null,
canHide: false,
};
export const created = columnHelper.accessor('created', {
id: 'created',
header: 'Created',
cell: ({ getValue }) => {
const value = getValue();
return isoDateFromTimestamp(value);
},
});

View file

@ -1,24 +1,22 @@
import { CellProps, Column } from 'react-table';
import _ from 'lodash';
import { CellContext } from '@tanstack/react-table';
import { EdgeGroup } from '@/react/edge/edge-groups/types';
import { useEdgeGroups } from '@/react/edge/edge-groups/queries/useEdgeGroups';
import { EdgeUpdateListItemResponse } from '../../queries/list';
export const groups: Column<EdgeUpdateListItemResponse> = {
Header: 'Groups',
accessor: 'edgeGroupIds',
Cell: GroupsCell,
disableFilters: true,
Filter: () => null,
canHide: false,
disableSortBy: true,
};
import { columnHelper } from './helper';
export const groups = columnHelper.accessor('edgeGroupIds', {
header: 'Groups',
cell: GroupsCell,
});
export function GroupsCell({
value: groupsIds,
}: CellProps<EdgeUpdateListItemResponse, Array<EdgeGroup['Id']>>) {
getValue,
}: CellContext<EdgeUpdateListItemResponse, Array<EdgeGroup['Id']>>) {
const groupsIds = getValue();
const groupsQuery = useEdgeGroups();
const groups = _.compact(

View file

@ -0,0 +1,5 @@
import { createColumnHelper } from '@tanstack/react-table';
import { EdgeUpdateListItemResponse } from '../../queries/list';
export const columnHelper = createColumnHelper<EdgeUpdateListItemResponse>();

View file

@ -1,12 +1,15 @@
import { buildNameColumn } from '@@/datatables/NameCell';
import { EdgeUpdateListItemResponse } from '../../queries/list';
import { created } from './created';
import { groups } from './groups';
import { name } from './name';
import { scheduleStatus } from './schedule-status';
import { scheduledTime } from './scheduled-time';
import { scheduleType } from './type';
export const columns = [
name,
buildNameColumn<EdgeUpdateListItemResponse>('name', 'id', '.item'),
scheduledTime,
groups,
scheduleType,

View file

@ -1,27 +0,0 @@
import { CellProps, Column } from 'react-table';
import { Link } from '@@/Link';
import { EdgeUpdateListItemResponse } from '../../queries/list';
export const name: Column<EdgeUpdateListItemResponse> = {
Header: 'Name',
accessor: 'name',
id: 'name',
Cell: NameCell,
disableFilters: true,
Filter: () => null,
canHide: false,
sortType: 'string',
};
export function NameCell({
value: name,
row,
}: CellProps<EdgeUpdateListItemResponse>) {
return (
<Link to=".item" params={{ id: row.original.id }}>
{name}
</Link>
);
}

View file

@ -1,27 +1,26 @@
import { CellProps, Column } from 'react-table';
import { CellContext } from '@tanstack/react-table';
import { EdgeUpdateListItemResponse } from '../../queries/list';
import { StatusType } from '../../types';
export const scheduleStatus: Column<EdgeUpdateListItemResponse> = {
Header: 'Status',
accessor: (row) => row.status,
disableFilters: true,
Filter: () => null,
canHide: false,
Cell: StatusCell,
disableSortBy: true,
};
import { columnHelper } from './helper';
export const scheduleStatus = columnHelper.accessor('status', {
header: 'Status',
cell: StatusCell,
});
function StatusCell({
value: status,
getValue,
row: {
original: { statusMessage },
},
}: CellProps<
}: CellContext<
EdgeUpdateListItemResponse,
EdgeUpdateListItemResponse['status']
>) {
const status = getValue();
switch (status) {
case StatusType.Failed:
return statusMessage;

View file

@ -1,11 +1,5 @@
import { Column } from 'react-table';
import { columnHelper } from './helper';
import { EdgeUpdateListItemResponse } from '../../queries/list';
export const scheduledTime: Column<EdgeUpdateListItemResponse> = {
Header: 'Scheduled Time & Date',
accessor: (row) => row.scheduledTime,
disableFilters: true,
Filter: () => null,
canHide: false,
};
export const scheduledTime = columnHelper.accessor('scheduledTime', {
header: 'Scheduled Time & Date',
});

View file

@ -1,13 +1,12 @@
import { Column } from 'react-table';
import { ScheduleType } from '../../types';
import { EdgeUpdateListItemResponse } from '../../queries/list';
export const scheduleType: Column<EdgeUpdateListItemResponse> = {
Header: 'Type',
accessor: (row) => ScheduleType[row.type],
disableFilters: true,
Filter: () => null,
canHide: false,
sortType: 'string',
};
import { columnHelper } from './helper';
export const scheduleType = columnHelper.accessor('type', {
header: 'Type',
cell: ({ getValue }) => {
const value = getValue();
return ScheduleType[value];
},
});

View file

@ -12,7 +12,7 @@ import { PageHeader } from '@@/PageHeader';
import { Datatable } from '@@/datatables';
import { Button } from '@@/buttons';
import { createPersistedStore } from '@@/datatables/types';
import { useSearchBarState } from '@@/datatables/SearchBar';
import { useTableState } from '@@/datatables/useTableState';
import { notificationsStore } from './notifications-store';
import { ToastNotification } from './types';
@ -32,8 +32,7 @@ export function NotificationsView() {
[];
const breadcrumbs = 'Notifications';
const settings = useStore(settingsStore);
const [search, setSearch] = useSearchBarState(storageKey);
const tableState = useTableState(settingsStore, storageKey);
const {
params: { id: activeItemId },
@ -47,17 +46,12 @@ export function NotificationsView() {
title="Notifications"
titleIcon={Bell}
dataset={userNotifications}
settingsManager={tableState}
emptyContentLabel="No notifications found"
totalCount={userNotifications.length}
renderTableActions={(selectedRows) => (
<TableActions selectedRows={selectedRows} />
)}
initialPageSize={settings.pageSize}
onPageSizeChange={settings.setPageSize}
initialSortBy={settings.sortBy}
onSortByChange={settings.setSortBy}
searchValue={search}
onSearchChange={setSearch}
getRowId={(row) => row.id}
highlightedItemId={activeItemId}
/>

View file

@ -1,14 +1,11 @@
import { Column } from 'react-table';
import { columnHelper } from './helper';
import { ToastNotification } from '../types';
export const details: Column<ToastNotification> = {
Header: 'Details',
accessor: 'details',
export const details = columnHelper.accessor('details', {
header: 'Details',
id: 'details',
disableFilters: true,
canHide: true,
Cell: ({ value }: { value: string }) => (
<div className="whitespace-normal">{value}</div>
),
};
cell: ({ getValue }) => {
const value = getValue();
return <div className="whitespace-normal">{value}</div>;
},
});

View file

@ -0,0 +1,5 @@
import { createColumnHelper } from '@tanstack/react-table';
import { ToastNotification } from '../types';
export const columnHelper = createColumnHelper<ToastNotification>();

View file

@ -1,13 +1,13 @@
import { Column } from 'react-table';
import { isoDate } from '@/portainer/filters/filters';
import { ToastNotification } from '../types';
import { columnHelper } from './helper';
export const time: Column<ToastNotification> = {
Header: 'Time',
accessor: (row) => (row.timeStamp ? isoDate(row.timeStamp) : '-'),
export const time = columnHelper.accessor('timeStamp', {
header: 'Time',
id: 'time',
disableFilters: true,
canHide: true,
};
cell: ({ getValue }) => {
const value = getValue();
return value ? isoDate(value) : '-';
},
});

View file

@ -1,11 +1,6 @@
import { Column } from 'react-table';
import { columnHelper } from './helper';
import { ToastNotification } from '../types';
export const title: Column<ToastNotification> = {
Header: 'Title',
accessor: 'title',
export const title = columnHelper.accessor('title', {
header: 'Title',
id: 'title',
disableFilters: true,
canHide: true,
};
});

View file

@ -1,11 +1,13 @@
import { Column } from 'react-table';
import _ from 'lodash';
import { ToastNotification } from '../types';
import { columnHelper } from './helper';
export const type: Column<ToastNotification> = {
Header: 'Type',
accessor: (row) => row.type.charAt(0).toUpperCase() + row.type.slice(1),
export const type = columnHelper.accessor('type', {
header: 'Type',
id: 'type',
disableFilters: true,
canHide: true,
};
cell: ({ getValue }) => {
const value = getValue();
return _.capitalize(value);
},
});

View file

@ -1,11 +1,10 @@
import { List } from 'lucide-react';
import { useStore } from 'zustand';
import { Datatable } from '@@/datatables';
import { createPersistedStore } from '@@/datatables/types';
import { useSearchBarState } from '@@/datatables/SearchBar';
import { useTableState } from '@@/datatables/useTableState';
import { useColumns } from './columns';
import { columns } from './columns';
import { FDOProfilesDatatableActions } from './FDOProfilesDatatableActions';
import { useFDOProfiles } from './useFDOProfiles';
@ -20,21 +19,15 @@ export interface FDOProfilesDatatableProps {
export function FDOProfilesDatatable({
isFDOEnabled,
}: FDOProfilesDatatableProps) {
const columns = useColumns();
const settings = useStore(settingsStore);
const [search, setSearch] = useSearchBarState(storageKey);
const tableState = useTableState(settingsStore, storageKey);
const { isLoading, profiles } = useFDOProfiles();
return (
<Datatable
columns={columns}
dataset={profiles}
initialPageSize={settings.pageSize}
onPageSizeChange={settings.setPageSize}
initialSortBy={settings.sortBy}
onSortByChange={settings.setSortBy}
searchValue={search}
onSearchChange={setSearch}
settingsManager={tableState}
title="Device Profiles"
titleIcon={List}
disableSelect={!isFDOEnabled}

View file

@ -1,14 +1,12 @@
import { Column } from 'react-table';
import { isoDateFromTimestamp } from '@/portainer/filters/filters';
import { Profile } from '@/portainer/hostmanagement/fdo/model';
export const created: Column<Profile> = {
Header: 'Created',
accessor: 'dateCreated',
import { columnHelper } from './helper';
export const created = columnHelper.accessor('dateCreated', {
header: 'Created',
id: 'created',
Cell: ({ value }) => isoDateFromTimestamp(value),
disableFilters: true,
canHide: true,
Filter: () => null,
};
cell: ({ getValue }) => {
const value = getValue();
return isoDateFromTimestamp(value);
},
});

View file

@ -0,0 +1,5 @@
import { createColumnHelper } from '@tanstack/react-table';
import { Profile } from '@/portainer/hostmanagement/fdo/model';
export const columnHelper = createColumnHelper<Profile>();

View file

@ -1,8 +1,10 @@
import { useMemo } from 'react';
import { Profile } from '@/portainer/hostmanagement/fdo/model';
import { buildNameColumn } from '@@/datatables/NameCell';
import { created } from './created';
import { name } from './name';
export function useColumns() {
return useMemo(() => [name, created], []);
}
export const columns = [
buildNameColumn<Profile>('name', 'id', 'portainer.endpoints.profile.edit'),
created,
];

View file

@ -1,30 +0,0 @@
import { CellProps, Column } from 'react-table';
import { useSref } from '@uirouter/react';
import { Profile } from '@/portainer/hostmanagement/fdo/model';
export const name: Column<Profile> = {
Header: 'Name',
accessor: 'name',
id: 'name',
Cell: NameCell,
disableFilters: true,
Filter: () => null,
canHide: true,
sortType: 'string',
};
export function NameCell({
value: name,
row: { original: profile },
}: CellProps<Profile>) {
const linkProps = useSref('portainer.endpoints.profile.edit', {
id: profile.id,
});
return (
<a href={linkProps.href} onClick={linkProps.onClick} title={name}>
{name}
</a>
);
}

View file

@ -31,11 +31,11 @@ export function TeamAssociationSelector({
);
return (
<div className="row">
<div className="col-sm-6">
<div className="flex">
<div className="w-1/2">
<UsersList users={usersNotInTeam} disabled={disabled} teamId={teamId} />
</div>
<div className="col-sm-6">
<div className="w-1/2">
<TeamMembersList
teamId={teamId}
disabled={disabled}

View file

@ -1,9 +1,3 @@
import {
useGlobalFilter,
usePagination,
useSortBy,
useTable,
} from 'react-table';
import { useMemo, useState } from 'react';
import { Users, UserX } from 'lucide-react';
@ -16,17 +10,11 @@ import {
useTeamMemberships,
} from '@/react/portainer/users/teams/queries';
import { Widget } from '@@/Widget';
import { PageSelector } from '@@/PaginationControls/PageSelector';
import { Button } from '@@/buttons';
import { Table } from '@@/datatables';
import { Input } from '@@/form-components/Input';
import { Datatable } from '@@/datatables';
import { name } from './name-column';
import { RowContext, RowProvider } from './RowContext';
import { teamRole } from './team-role-column';
const columns = [name, teamRole];
import { columns } from './columns';
interface Props {
users: User[];
@ -45,39 +33,10 @@ export function TeamMembersList({ users, roles, disabled, teamId }: Props) {
const [search, setSearch] = useState('');
const [pageSize, setPageSize] = useState(10);
const [sortBy, setSortBy] = useState({ id: 'name', desc: false });
const { isAdmin } = useUser();
const {
getTableProps,
getTableBodyProps,
headerGroups,
page,
prepareRow,
gotoPage,
setPageSize: setPageSizeInternal,
setGlobalFilter: setGlobalFilterInternal,
state: { pageIndex },
setSortBy,
rows,
} = useTable<User>(
{
defaultCanFilter: false,
columns,
data: users,
initialState: {
pageSize,
sortBy: [{ id: 'name', desc: false }],
globalFilter: search,
},
},
useGlobalFilter,
useSortBy,
usePagination
);
const tableProps = getTableProps();
const tbodyProps = getTableBodyProps();
const rowContext = useMemo<RowContext>(
() => ({
getRole(userId: UserId) {
@ -88,124 +47,40 @@ export function TeamMembersList({ users, roles, disabled, teamId }: Props) {
}),
[roles, disabled, teamId]
);
return (
<Widget>
<Widget.Title icon={Users} title="Team members">
Items per page:
<select
value={pageSize}
onChange={handlePageSizeChange}
className="space-left"
>
<option value={Number.MAX_SAFE_INTEGER}>All</option>
<option value={10}>10</option>
<option value={25}>25</option>
<option value={50}>50</option>
<option value={100}>100</option>
</select>
</Widget.Title>
<Widget.Taskbar className="col-sm-12 nopadding">
<div className="col-sm-12 col-md-6 nopadding">
{isAdmin && (
<RowProvider context={rowContext}>
<Datatable<User>
dataset={users}
columns={columns}
titleIcon={Users}
title="Team members"
renderTableActions={() =>
isAdmin && (
<Button
onClick={() =>
handleRemoveMembers(rows.map(({ original }) => original.Id))
}
onClick={() => handleRemoveMembers(users.map((user) => user.Id))}
disabled={disabled || users.length === 0}
icon={UserX}
>
Remove all users
</Button>
)}
</div>
<div className="col-sm-12 col-md-6 nopadding">
<Input
type="text"
id="filter-users"
value={search}
onChange={handleSearchBarChange}
placeholder="Filter..."
className="input-sm"
/>
</div>
</Widget.Taskbar>
<Widget.Body className="nopadding">
<Table
className={tableProps.className}
role={tableProps.role}
style={tableProps.style}
>
<thead>
{headerGroups.map((headerGroup) => {
const { key, className, role, style } =
headerGroup.getHeaderGroupProps();
return (
<Table.HeaderRow<User>
key={key}
className={className}
role={role}
style={style}
headers={headerGroup.headers}
onSortChange={handleSortChange}
/>
);
})}
</thead>
<tbody
className={tbodyProps.className}
role={tbodyProps.role}
style={tbodyProps.style}
>
<Table.Content
emptyContent="No users."
prepareRow={prepareRow}
rows={page}
renderRow={(row, { key, className, role, style }) => (
<RowProvider context={rowContext} key={key}>
<Table.Row<User>
cells={row.cells}
key={key}
className={className}
role={role}
style={style}
/>
</RowProvider>
)}
/>
</tbody>
</Table>
<Table.Footer>
{pageSize !== 0 && (
<div className="pagination-controls">
<PageSelector
maxSize={5}
onPageChange={(p) => gotoPage(p - 1)}
currentPage={pageIndex + 1}
itemsPerPage={pageSize}
totalCount={rows.length}
/>
</div>
)}
</Table.Footer>
</Widget.Body>
</Widget>
)
}
disableSelect
settingsManager={{
pageSize,
setPageSize,
sortBy,
setSortBy: handleSetSort,
search,
setSearch,
}}
/>
</RowProvider>
);
function handlePageSizeChange(e: React.ChangeEvent<HTMLSelectElement>) {
const pageSize = parseInt(e.target.value, 10);
setPageSize(pageSize);
setPageSizeInternal(pageSize);
}
function handleSearchBarChange(e: React.ChangeEvent<HTMLInputElement>) {
const { value } = e.target;
setSearch(value);
setGlobalFilterInternal(value);
}
function handleSortChange(id: string, desc: boolean) {
setSortBy([{ id, desc }]);
function handleSetSort(colId: string, desc: boolean) {
setSortBy({ id: colId, desc });
}
function handleRemoveMembers(userIds: UserId[]) {

View file

@ -0,0 +1,5 @@
import { createColumnHelper } from '@tanstack/react-table';
import { User } from '@/portainer/users/types';
export const columnHelper = createColumnHelper<User>();

View file

@ -0,0 +1,4 @@
import { name } from './name-column';
import { teamRole } from './team-role-column';
export const columns = [name, teamRole];

View file

@ -1,5 +1,5 @@
import { CellProps, Column } from 'react-table';
import { MinusCircle } from 'lucide-react';
import { CellContext } from '@tanstack/react-table';
import { User, UserId } from '@/portainer/users/types';
import { notifySuccess } from '@/portainer/services/notifications';
@ -10,23 +10,21 @@ import {
import { Button } from '@@/buttons';
import { useRowContext } from './RowContext';
import { useRowContext } from '../RowContext';
export const name: Column<User> = {
Header: 'Name',
accessor: (row) => row.Username,
import { columnHelper } from './helper';
export const name = columnHelper.accessor('Username', {
header: 'Name',
id: 'name',
Cell: NameCell,
disableFilters: true,
Filter: () => null,
canHide: false,
sortType: 'string',
};
cell: NameCell,
});
export function NameCell({
value: name,
getValue,
row: { original: user },
}: CellProps<User, string>) {
}: CellContext<User, string>) {
const name = getValue();
const { disabled, teamId } = useRowContext();
const membershipsQuery = useTeamMemberships(teamId);
@ -42,7 +40,7 @@ export function NameCell({
<Button
color="link"
className="space-left nopadding"
className="space-left !p-0"
onClick={() => handleRemoveMember(user.Id)}
disabled={disabled}
icon={MinusCircle}

View file

@ -1,5 +1,5 @@
import { CellProps, Column } from 'react-table';
import { User as UserIcon, UserPlus, UserX } from 'lucide-react';
import { CellContext } from '@tanstack/react-table';
import { User } from '@/portainer/users/types';
import { useUser as useCurrentUser } from '@/react/hooks/useUser';
@ -13,20 +13,22 @@ import {
import { Button } from '@@/buttons';
import { Icon } from '@@/Icon';
import { useRowContext } from './RowContext';
import { useRowContext } from '../RowContext';
export const teamRole: Column<User> = {
Header: 'Team Role',
accessor: 'Id',
import { columnHelper } from './helper';
export const teamRole = columnHelper.accessor('Id', {
header: 'Team Role',
id: 'role',
Cell: RoleCell,
disableFilters: true,
Filter: () => null,
canHide: false,
sortType: 'string',
};
cell: RoleCell,
});
export function RoleCell({
row: { original: user },
getValue,
}: CellContext<User, User['Id']>) {
const id = getValue();
export function RoleCell({ row: { original: user } }: CellProps<User>) {
const { getRole, disabled, teamId } = useRowContext();
const membershipsQuery = useTeamMemberships(teamId);
const updateRoleMutation = useUpdateRoleMutation(
@ -34,7 +36,7 @@ export function RoleCell({ row: { original: user } }: CellProps<User>) {
membershipsQuery.data
);
const role = getRole(user.Id);
const role = getRole(id);
const { isAdmin } = useCurrentUser();

View file

@ -1,9 +1,3 @@
import {
useGlobalFilter,
usePagination,
useSortBy,
useTable,
} from 'react-table';
import { useMemo, useState } from 'react';
import { UserPlus, Users } from 'lucide-react';
@ -13,12 +7,8 @@ import { notifySuccess } from '@/portainer/services/notifications';
import { useAddMemberMutation } from '@/react/portainer/users/teams/queries';
import { TeamId } from '@/react/portainer/users/teams/types';
import { Widget } from '@@/Widget';
import { PageSelector } from '@@/PaginationControls/PageSelector';
import { Button } from '@@/buttons';
import { Table } from '@@/datatables';
import { TableFooter } from '@@/datatables/TableFooter';
import { Input } from '@@/form-components/Input';
import { Datatable } from '@@/datatables';
import { name } from './name-column';
import { RowProvider } from './RowContext';
@ -35,158 +25,45 @@ export function UsersList({ users, disabled, teamId }: Props) {
const [search, setSearch] = useState('');
const [pageSize, setPageSize] = useState(10);
const addMemberMutation = useAddMemberMutation(teamId);
const [sortBy, setSortBy] = useState({ id: 'name', desc: false });
const { isAdmin } = useUser();
const {
getTableProps,
getTableBodyProps,
headerGroups,
page,
prepareRow,
gotoPage,
setPageSize: setPageSizeInternal,
setGlobalFilter: setGlobalFilterInternal,
state: { pageIndex },
setSortBy,
rows,
} = useTable(
{
defaultCanFilter: false,
columns,
data: users,
initialState: {
pageSize,
sortBy: [{ id: 'name', desc: false }],
globalFilter: search,
},
},
useGlobalFilter,
useSortBy,
usePagination
);
const tableProps = getTableProps();
const tbodyProps = getTableBodyProps();
const rowContext = useMemo(() => ({ disabled, teamId }), [disabled, teamId]);
return (
<Widget>
<Widget.Title icon={Users} title="Users">
Items per page:
<select
value={pageSize}
onChange={handlePageSizeChange}
className="space-left"
>
<option value={Number.MAX_SAFE_INTEGER}>All</option>
<option value={10}>10</option>
<option value={25}>25</option>
<option value={50}>50</option>
<option value={100}>100</option>
</select>
</Widget.Title>
<Widget.Taskbar className="col-sm-12 nopadding">
<div className="col-sm-12 col-md-6 nopadding">
{isAdmin && (
<RowProvider context={rowContext}>
<Datatable<User>
dataset={users}
columns={columns}
titleIcon={Users}
title="Users"
renderTableActions={() =>
isAdmin && (
<Button
onClick={() =>
handleAddAllMembers(rows.map((row) => row.original.Id))
}
disabled={disabled || rows.length === 0}
onClick={() => handleAddAllMembers(users.map((u) => u.Id))}
disabled={disabled || users.length === 0}
icon={UserPlus}
>
Add all users
</Button>
)}
</div>
<div className="col-sm-12 col-md-6 nopadding">
<Input
type="text"
id="filter-users"
value={search}
onChange={handleSearchBarChange}
placeholder="Filter..."
className="input-sm"
/>
</div>
</Widget.Taskbar>
<Widget.Body className="nopadding">
<Table
className={tableProps.className}
role={tableProps.role}
style={tableProps.style}
>
<thead>
{headerGroups.map((headerGroup) => {
const { key, className, role, style } =
headerGroup.getHeaderGroupProps();
return (
<Table.HeaderRow<User>
key={key}
className={className}
role={role}
style={style}
headers={headerGroup.headers}
onSortChange={handleSortChange}
/>
);
})}
</thead>
<tbody
className={tbodyProps.className}
role={tbodyProps.role}
style={tbodyProps.style}
>
<Table.Content
emptyContent="No users."
prepareRow={prepareRow}
rows={page}
renderRow={(row, { key, className, role, style }) => (
<RowProvider context={rowContext} key={key}>
<Table.Row<User>
cells={row.cells}
key={key}
className={className}
role={role}
style={style}
/>
</RowProvider>
)}
/>
</tbody>
</Table>
<TableFooter>
{pageSize !== 0 && (
<div className="pagination-controls">
<PageSelector
maxSize={5}
onPageChange={(p) => gotoPage(p - 1)}
currentPage={pageIndex + 1}
itemsPerPage={pageSize}
totalCount={rows.length}
/>
</div>
)}
</TableFooter>
</Widget.Body>
</Widget>
)
}
disableSelect
settingsManager={{
pageSize,
setPageSize,
sortBy,
setSortBy: handleSetSort,
search,
setSearch,
}}
/>
</RowProvider>
);
function handlePageSizeChange(e: React.ChangeEvent<HTMLSelectElement>) {
const pageSize = parseInt(e.target.value, 10);
setPageSize(pageSize);
setPageSizeInternal(pageSize);
}
function handleSearchBarChange(e: React.ChangeEvent<HTMLInputElement>) {
const { value } = e.target;
setSearch(value);
setGlobalFilterInternal(value);
}
function handleSortChange(id: string, desc: boolean) {
setSortBy([{ id, desc }]);
function handleSetSort(colId: string, desc: boolean) {
setSortBy({ id: colId, desc });
}
function handleAddAllMembers(userIds: UserId[]) {

View file

@ -1,5 +1,5 @@
import { CellProps, Column } from 'react-table';
import { PlusCircle } from 'lucide-react';
import { CellContext, ColumnDef } from '@tanstack/react-table';
import { User } from '@/portainer/users/types';
import { notifySuccess } from '@/portainer/services/notifications';
@ -9,21 +9,18 @@ import { Button } from '@@/buttons';
import { useRowContext } from './RowContext';
export const name: Column<User> = {
Header: 'Name',
accessor: (row) => row.Username,
export const name: ColumnDef<User, string> = {
header: 'Name',
accessorFn: (row) => row.Username,
id: 'name',
Cell: NameCell,
disableFilters: true,
Filter: () => null,
canHide: false,
sortType: 'string',
cell: NameCell,
};
export function NameCell({
value: name,
getValue,
row: { original: user },
}: CellProps<User, string>) {
}: CellContext<User, string>) {
const name = getValue();
const { disabled, teamId } = useRowContext();
const addMemberMutation = useAddMemberMutation(teamId);

View file

@ -1,7 +1,6 @@
import { Column } from 'react-table';
import { useMutation, useQueryClient } from 'react-query';
import { Trash2, Users } from 'lucide-react';
import { useStore } from 'zustand';
import { ColumnDef } from '@tanstack/react-table';
import { notifySuccess } from '@/portainer/services/notifications';
import { promiseSequence } from '@/portainer/helpers/promise-utils';
@ -13,13 +12,13 @@ import { Datatable } from '@@/datatables';
import { Button } from '@@/buttons';
import { buildNameColumn } from '@@/datatables/NameCell';
import { createPersistedStore } from '@@/datatables/types';
import { useSearchBarState } from '@@/datatables/SearchBar';
import { useTableState } from '@@/datatables/useTableState';
const storageKey = 'teams';
const columns: readonly Column<Team>[] = [
buildNameColumn('Name', 'Id', 'portainer.teams.team'),
] as const;
const columns: ColumnDef<Team>[] = [
buildNameColumn<Team>('Name', 'Id', 'portainer.teams.team'),
];
interface Props {
teams: Team[];
@ -30,19 +29,13 @@ const settingsStore = createPersistedStore(storageKey);
export function TeamsDatatable({ teams, isAdmin }: Props) {
const { handleRemove } = useRemoveMutation();
const settings = useStore(settingsStore);
const [search, setSearch] = useSearchBarState(storageKey);
const tableState = useTableState(settingsStore, storageKey);
return (
<Datatable
<Datatable<Team>
dataset={teams}
columns={columns}
initialPageSize={settings.pageSize}
onPageSizeChange={settings.setPageSize}
initialSortBy={settings.sortBy}
onSortByChange={settings.setSortBy}
searchValue={search}
onSearchChange={setSearch}
settingsManager={tableState}
title="Teams"
titleIcon={Users}
renderTableActions={(selectedRows) =>