@patternfly/react-core#Skeleton JavaScript Examples

The following examples show how to use @patternfly/react-core#Skeleton. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: ToolbarFooter.js    From edge-frontend with Apache License 2.0 6 votes vote down vote up
ToolbarFooter = ({
  isLoading,
  count,
  perPage,
  setPerPage,
  page,
  setPage,
}) => {
  return (
    <Toolbar style={{ padding: '16px' }} id="toolbar-footer">
      <ToolbarContent>
        <ToolbarItem variant="pagination" align={{ default: 'alignRight' }}>
          {isLoading ? (
            <Skeleton width="400px" />
          ) : count > 0 ? (
            <Pagination
              data-testid="pagination-footer-test-id"
              itemCount={count}
              perPage={perPage}
              page={page}
              onSetPage={(_e, pageNumber) => setPage(pageNumber)}
              widgetId="pagination-options-menu-top"
              onPerPageSelect={(_e, perPage) => setPerPage(perPage)}
            />
          ) : null}
        </ToolbarItem>
      </ToolbarContent>
    </Toolbar>
  );
}
Example #2
Source File: ToolbarHeader.js    From edge-frontend with Apache License 2.0 5 votes vote down vote up
ToolbarHeader = ({
  toolbarButtons,
  filters,
  setFilterValues,
  filterValues,
  chipsArray,
  setChipsArray,
  isLoading,
  count,
  perPage,
  setPerPage,
  page,
  setPage,
  toggleButton,
  toggleAction,
  toggleState,
  children,
  kebabItems,
}) => {
  return (
    <Toolbar
      style={{ padding: '16px' }}
      id="toolbar-header"
      data-testid="toolbar-header-testid"
    >
      <ToolbarContent>
        <FilterControls
          filters={filters}
          filterValues={filterValues}
          setFilterValues={setFilterValues}
        >
          {children}
        </FilterControls>
        {toolbarButtons && <ToolbarButtons buttons={toolbarButtons} />}
        {toggleButton && (
          <ToggleGroup>
            {toggleButton.map((btn) => (
              <ToggleGroupItem
                key={btn.key}
                text={btn.title}
                isSelected={toggleState === btn.key}
                onChange={() => toggleAction(btn.key)}
              />
            ))}
          </ToggleGroup>
        )}
        {kebabItems && <ToolbarKebab kebabItems={kebabItems} />}
        <ToolbarItem variant="pagination" align={{ default: 'alignRight' }}>
          {isLoading ? (
            <Skeleton width="200px" />
          ) : count > 0 ? (
            <Pagination
              data-testid="pagination-header-test-id"
              itemCount={count}
              perPage={perPage}
              page={page}
              onSetPage={(_e, pageNumber) => setPage(pageNumber)}
              widgetId="pagination-options-menu-top"
              onPerPageSelect={(_e, perPage) => setPerPage(perPage)}
              isCompact
            />
          ) : null}
        </ToolbarItem>
      </ToolbarContent>
      <ToolbarContent>
        <ToolbarItem variant="chip-group" spacer={{ default: 'spacerNone' }}>
          <FilterChip
            filterValues={filterValues}
            setFilterValues={setFilterValues}
            chipsArray={chipsArray}
            setChipsArray={setChipsArray}
            setPage={setPage}
          />
        </ToolbarItem>
      </ToolbarContent>
    </Toolbar>
  );
}
Example #3
Source File: LogsTable.js    From sed-frontend with Apache License 2.0 5 votes vote down vote up
LogsTable = () => {
  const [opened, setOpened] = useState([]);
  const dispatch = useDispatch();
  const logLoaded = useSelector(
    ({ logReducer }) => logReducer?.loaded || false
  );
  const rows = useSelector(({ logReducer }) => logReducer?.results || []);
  const pagination = useSelector(
    ({ logReducer }) => ({
      itemCount: logReducer?.total,
      perPage: logReducer?.limit,
      page:
        Math.floor((logReducer?.offset || 0) / (logReducer?.limit || 0)) + 1,
    }),
    shallowEqual
  );
  useEffect(() => {
    dispatch(fetchLog());
  }, []);
  const onCollapse = (_e, _key, isOpen, { id }) => {
    setOpened(() =>
      isOpen ? [...opened, id] : opened.filter((openId) => openId !== id)
    );
  };

  const setPage = useCallback(
    (_e, pageNumber) =>
      dispatch(fetchLog({ page: pageNumber, perPage: pagination.perPage })),
    [dispatch, pagination.perPage]
  );

  const setPerPage = useCallback(
    (_e, perPage) => dispatch(fetchLog({ page: 1, perPage })),
    [dispatch]
  );

  return (
    <Fragment>
      <PrimaryToolbar
        pagination={
          logLoaded ? (
            {
              ...pagination,
              onSetPage: setPage,
              onPerPageSelect: setPerPage,
            }
          ) : (
            <Skeleton width="33%" />
          )
        }
      />
      {logLoaded ? (
        <Table
          aria-label="Logs table"
          variant={TableVariant.compact}
          rows={rowsMapper(rows, opened)}
          cells={columns}
          onCollapse={onCollapse}
        >
          <TableHeader />
          <TableBody />
        </Table>
      ) : (
        <SkeletonTable colSize={3} rowSize={10} />
      )}
      <TableToolbar isFooter>
        {logLoaded ? (
          <Pagination
            {...pagination}
            variant={PaginationVariant.bottom}
            onSetPage={setPage}
            onPerPageSelect={setPerPage}
          />
        ) : (
          <Skeleton width="33%" />
        )}
      </TableToolbar>
    </Fragment>
  );
}
Example #4
Source File: GroupsDetail.js    From edge-frontend with Apache License 2.0 4 votes vote down vote up
GroupsDetail = () => {
  const dispatch = useDispatch();
  const params = useParams();
  const history = useHistory();
  const { groupId } = params;

  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
  const [isAddModalOpen, setIsAddModalOpen] = useState(false);
  const [removeModal, setRemoveModal] = useState({
    isOpen: false,
    name: '',
    deviceId: null,
  });
  const [updateModal, setUpdateModal] = useState({
    isOpen: false,
    deviceData: null,
    imageData: null,
  });
  const [response, fetchDevices] = useApi({
    api: getGroupById,
    id: groupId,
    tableReload: true,
  });
  const { data, isLoading, hasError } = response;
  const groupName = data?.DeviceGroup?.Name;
  const [deviceIds, getDeviceIds] = useState([]);
  const [hasModalSubmitted, setHasModalSubmitted] = useState(false);
  const [modalState, setModalState] = useState({ id: null, name: '' });
  const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
  const [isRenameModalOpen, setIsRenameModalOpen] = useState(false);

  const handleDeleteModal = (id, name) => {
    setModalState({ id, name });
    setIsDeleteModalOpen(true);
  };

  const handleRenameModal = (id, name) => {
    setModalState({ id, name });
    setIsRenameModalOpen(true);
  };

  const removeDeviceLabel = () =>
    `Do you want to remove ${
      deviceIds.length > 0
        ? `${deviceIds.length} system${deviceIds.length === 1 ? '' : 's'}`
        : `${removeModal.name}`
    } from ${groupName}?`;

  useEffect(() => {
    history.push({
      pathname: history.location.pathname,
      search: stateToUrlSearch('add_system_modal=true', isAddModalOpen),
    });
  }, [isAddModalOpen]);

  const handleSingleDeviceRemoval = () => {
    const statusMessages = {
      onSuccess: {
        title: 'Success',
        description: `${removeModal.name} has been removed successfully`,
      },
      onError: { title: 'Error', description: 'Failed to remove device' },
    };
    apiWithToast(
      dispatch,
      () => removeDeviceFromGroupById(groupId, removeModal.deviceId),
      statusMessages
    );
    setTimeout(() => setHasModalSubmitted(true), 800);
  };

  const handleBulkDeviceRemoval = () => {
    const statusMessages = {
      onSuccess: {
        title: 'Success',
        description: `${deviceIds.length} systems have been removed successfully`,
      },
      onError: { title: 'Error', description: 'failed to remove systems' },
    };
    apiWithToast(
      dispatch,
      () =>
        removeDevicesFromGroup(
          parseInt(groupId),
          deviceIds.map((device) => ({ ID: device.deviceID }))
        ),
      statusMessages
    );
    setTimeout(() => setHasModalSubmitted(true), 800);
  };

  return (
    <>
      <PageHeader className="pf-m-light">
        {groupName ? (
          <Breadcrumb>
            <BreadcrumbItem>
              <Link to={`${paths['fleet-management']}`}>Groups</Link>
            </BreadcrumbItem>
            <BreadcrumbItem>{groupName}</BreadcrumbItem>
          </Breadcrumb>
        ) : (
          <Breadcrumb isActive>
            <Skeleton width="100px" />
          </Breadcrumb>
        )}
        <Flex justifyContent={{ default: 'justifyContentSpaceBetween' }}>
          <FlexItem>
            {groupName ? (
              <PageHeaderTitle title={groupName} />
            ) : (
              <Skeleton width="150px" />
            )}
          </FlexItem>
          <FlexItem>
            <Dropdown
              position={DropdownPosition.right}
              toggle={
                <DropdownToggle
                  id="image-set-details-dropdown"
                  toggleIndicator={CaretDownIcon}
                  onToggle={(newState) => setIsDropdownOpen(newState)}
                  isDisabled={false}
                >
                  Actions
                </DropdownToggle>
              }
              isOpen={isDropdownOpen}
              dropdownItems={[
                <DropdownItem
                  key="delete-device-group"
                  onClick={() => handleDeleteModal(groupId, groupName)}
                >
                  Delete group
                </DropdownItem>,
                <DropdownItem
                  key="rename-device-group"
                  onClick={() => handleRenameModal(groupId, groupName)}
                >
                  Rename group
                </DropdownItem>,
                <DropdownItem
                  key="update-all-devices"
                  isDisabled={canUpdateSelectedDevices({
                    deviceData: data?.DevicesView?.devices?.map((device) => ({
                      imageSetId: device?.ImageSetID,
                    })),
                    imageData: data?.DevicesView?.devices?.some(
                      (device) => device.ImageID
                    ),
                  })}
                  onClick={() => {
                    setIsDropdownOpen(false);
                    setUpdateModal((prevState) => ({
                      ...prevState,
                      isOpen: true,
                      deviceData: data?.DevicesView?.devices?.map((device) => ({
                        id: device?.DeviceUUID,
                        display_name:
                          device?.DeviceName === ''
                            ? 'localhost'
                            : device?.DeviceName,
                      })),
                      imageSetId: data?.DevicesView?.devices.find(
                        (device) => device.ImageSetID
                      )?.ImageSetID,
                    }));
                  }}
                >
                  Update
                </DropdownItem>,
              ]}
            />
          </FlexItem>
        </Flex>
      </PageHeader>
      <Main className="edge-devices">
        {!emptyStateNoFliters(
          isLoading,
          data?.DeviceGroup?.Devices.length,
          history
        ) ? (
          <DeviceTable
            data={data?.DevicesView?.devices || []}
            count={data?.DevicesView?.total}
            isLoading={isLoading}
            hasError={hasError}
            hasCheckbox={true}
            handleSingleDeviceRemoval={handleSingleDeviceRemoval}
            kebabItems={[
              {
                isDisabled: !(deviceIds.length > 0),
                title: 'Remove from group',
                onClick: () =>
                  setRemoveModal({
                    name: '',
                    deviceId: null,
                    isOpen: true,
                  }),
              },
              {
                isDisabled: canUpdateSelectedDevices({
                  deviceData: deviceIds,
                  imageData: deviceIds[0]?.updateImageData,
                }),
                title: 'Update selected',
                onClick: () =>
                  setUpdateModal((prevState) => ({
                    ...prevState,
                    isOpen: true,
                    deviceData: [...deviceIds],
                    imageSetId: deviceIds.find((device) => device?.imageSetId)
                      .imageSetId,
                  })),
              },
            ]}
            selectedItems={getDeviceIds}
            setRemoveModal={setRemoveModal}
            setIsAddModalOpen={setIsAddModalOpen}
            setUpdateModal={setUpdateModal}
            hasModalSubmitted={hasModalSubmitted}
            setHasModalSubmitted={setHasModalSubmitted}
            fetchDevices={fetchDevices}
            isAddSystemsView={true}
          />
        ) : (
          <Flex justifyContent={{ default: 'justifyContentCenter' }}>
            <Empty
              icon="plus"
              title="Add systems to the group"
              body="Create groups to help manage your systems more effectively."
              primaryAction={{
                text: 'Add systems',
                click: () => setIsAddModalOpen(true),
              }}
              secondaryActions={[
                {
                  type: 'link',
                  title: 'Learn more about system groups',
                  link: 'https://access.redhat.com/documentation/en-us/edge_management/2022/html-single/working_with_systems_in_the_edge_management_application/index',
                },
              ]}
            />
          </Flex>
        )}
      </Main>
      {isAddModalOpen && (
        <AddSystemsToGroupModal
          groupId={groupId}
          closeModal={() => setIsAddModalOpen(false)}
          isOpen={isAddModalOpen}
          reloadData={fetchDevices}
          groupName={data?.DeviceGroup?.Name}
        />
      )}
      {removeModal.isOpen && (
        <Modal
          isOpen={removeModal.isOpen}
          openModal={() => setRemoveModal(false)}
          title={'Remove from group'}
          submitLabel={'Remove'}
          variant="danger"
          schema={{
            fields: [
              {
                component: componentTypes.PLAIN_TEXT,
                name: 'warning-text',
                label: removeDeviceLabel(),
              },
            ],
          }}
          onSubmit={
            removeModal.deviceId
              ? handleSingleDeviceRemoval
              : handleBulkDeviceRemoval
          }
          reloadData={fetchDevices}
        />
      )}

      {updateModal.isOpen && (
        <Suspense
          fallback={
            <Bullseye>
              <Spinner />
            </Bullseye>
          }
        >
          <UpdateDeviceModal
            navigateBack={() => {
              history.push({ pathname: history.location.pathname });
              setUpdateModal((prevState) => {
                return {
                  ...prevState,
                  isOpen: false,
                };
              });
            }}
            setUpdateModal={setUpdateModal}
            updateModal={updateModal}
            refreshTable={fetchDevices}
          />
        </Suspense>
      )}
      {isDeleteModalOpen && (
        <DeleteGroupModal
          isModalOpen={isDeleteModalOpen}
          setIsModalOpen={setIsDeleteModalOpen}
          reloadData={() => history.push(paths['fleet-management'])}
          modalState={modalState}
        />
      )}
      {isRenameModalOpen && (
        <RenameGroupModal
          isModalOpen={isRenameModalOpen}
          setIsModalOpen={setIsRenameModalOpen}
          reloadData={() => fetchDevices()}
          modalState={modalState}
        />
      )}
    </>
  );
}
Example #5
Source File: DetailsHeader.js    From edge-frontend with Apache License 2.0 4 votes vote down vote up
DetailsHead = ({ imageData, imageVersion, openUpdateWizard }) => {
  const [isOpen, setIsOpen] = useState(false);
  const [data, setData] = useState({});

  useEffect(() => {
    setData(imageData?.data?.Data);
  }, [imageData]);

  return (
    <>
      {!imageData.isLoading && imageData.hasError ? (
        <Breadcrumb>
          <BreadcrumbItem>
            <Link to={paths['manage-images']}>Back to Manage Images</Link>
          </BreadcrumbItem>
        </Breadcrumb>
      ) : (
        <>
          <Breadcrumb>
            <BreadcrumbItem>
              <Link to={paths['manage-images']}>Manage Images</Link>
            </BreadcrumbItem>
            {imageVersion ? (
              <BreadcrumbItem>
                <Link to={`${paths['manage-images']}/${data?.image_set?.ID}`}>
                  {data?.image_set?.Name}
                </Link>
              </BreadcrumbItem>
            ) : (
              <BreadcrumbItem isActive>
                {data?.image_set?.Name || <Skeleton width="100px" />}
              </BreadcrumbItem>
            )}
            {imageVersion && (
              <BreadcrumbItem isActive>
                {imageVersion?.image?.Version}
              </BreadcrumbItem>
            )}
          </Breadcrumb>

          <TextContent>
            <Split>
              <SplitItem>
                <TextList component="dl">
                  <TextListItem
                    component="h1"
                    className="grid-align-center pf-u-mb-0"
                  >
                    {data?.image_set?.Name || <Skeleton width="150px" />}
                  </TextListItem>
                  <TextListItem className="pf-u-pt-sm" component="dd">
                    {data?.Status || data?.images?.[0]?.image?.Status ? (
                      <Status
                        type={data?.images?.[0]?.image?.Status.toLowerCase()}
                      />
                    ) : (
                      <Skeleton width="100px" />
                    )}
                  </TextListItem>
                  {imageVersion?.image?.UpdatedAt ||
                  data?.images?.[0].image?.UpdatedAt ? (
                    <TextListItem component="p">
                      {`Last updated `}
                      <DateFormat
                        date={
                          imageVersion
                            ? imageVersion?.image?.UpdatedAt
                            : data?.images?.[0].image?.UpdatedAt
                        }
                      />
                    </TextListItem>
                  ) : (
                    <Skeleton width="200px" />
                  )}
                </TextList>
              </SplitItem>
              <SplitItem isFilled></SplitItem>
              <SplitItem>
                <Dropdown
                  position={DropdownPosition.right}
                  toggle={
                    <DropdownToggle
                      id="image-set-details-dropdown"
                      toggleIndicator={CaretDownIcon}
                      onToggle={(newState) => setIsOpen(newState)}
                      isDisabled={
                        (imageVersion
                          ? imageVersion?.image?.Status
                          : data?.Images?.[0]?.Status) === 'BUILDING' || false
                      }
                    >
                      Actions
                    </DropdownToggle>
                  }
                  isOpen={isOpen}
                  dropdownItems={dropdownItems(
                    data,
                    imageVersion,
                    openUpdateWizard
                  )}
                />
              </SplitItem>
            </Split>
          </TextContent>
        </>
      )}
    </>
  );
}
Example #6
Source File: ImageDetailTab.js    From edge-frontend with Apache License 2.0 4 votes vote down vote up
ImageDetailTab = ({ imageData, imageVersion }) => {
  const [data, setData] = useState({});

  useEffect(() => {
    imageVersion
      ? setData(imageVersion)
      : setData(imageData?.data?.Data?.images?.[0]);
  }, [imageData, imageVersion]);

  const createSkeleton = (rows) =>
    [...Array(rows * 2)].map((_, key) => <Skeleton width="180px" key={key} />);

  const dateFormat = () => <DateFormat date={data?.image?.['CreatedAt']} />;

  const detailsMapper = {
    Version: 'Version',
    Created: () => dateFormat(),
    'Type(s)': () =>
      data?.image?.['OutputTypes']?.map((outputType, index) => (
        <div key={index}>{outputType}</div>
      )),
    Release: () => distributionMapper?.[data?.image?.['Distribution']],
    //Size: 'Size',
    Description: 'Description',
  };

  const userInfoMapper = {
    Username: () => data?.image?.Installer?.Username,
    'SSH key': () => data?.image?.Installer?.SshKey,
  };
  const renderAdditionalPackageLink = () => {
    return (
      <Link
        to={`${paths['manage-images']}/${data?.image?.ImageSetID}/versions/${data?.image?.ID}/packages/additional`}
      >
        {data?.additional_packages}
      </Link>
    );
  };

  const renderTotalPackageLink = () => {
    return (
      <Link
        to={`${paths['manage-images']}/${data?.image?.ImageSetID}/versions/${data?.image?.ID}/packages/all`}
      >
        {data?.packages}
      </Link>
    );
  };

  const packageMapper = {
    'Total additional packages': renderAdditionalPackageLink,
    'Total packages': renderTotalPackageLink,
  };

  const packageDiffMapper = {
    Added: () => data?.update_added,
    Removed: () => data?.update_removed,
    Updated: () => data?.update_updated,
  };

  if (data?.image?.Installer?.Checksum) {
    detailsMapper['SHA-256 checksum'] = () => data?.image?.Installer?.Checksum;
  }

  if (data?.image?.Commit?.OSTreeCommit) {
    detailsMapper['Ostree commit hash'] = () =>
      data?.image?.Commit?.OSTreeCommit;
  }

  const buildTextList = (labelsToValueMapper) =>
    data
      ? Object.entries(labelsToValueMapper).map(([label, value], index) => {
          return (
            <Fragment key={index}>
              <TextListItem
                className="details-label"
                component={TextListItemVariants.dt}
              >
                {label}
              </TextListItem>
              {label === 'SHA-256 checksum' ||
              label === 'Ostree commit hash' ||
              (label === 'SSH key' && value()) ? (
                <TextListItem component={TextListItemVariants.dd}>
                  <ClipboardCopy
                    hoverTip="Copy"
                    clickTip="Copied"
                    variant="expansion"
                    className="pf-u-text-break-word"
                    id={`${label
                      .replace(/\s+/g, '-')
                      .toLowerCase()}-clipboard-copy`}
                  >
                    {typeof value === 'function'
                      ? value() || 'Unavailable'
                      : data?.image?.[value] || 'Unavailable'}
                  </ClipboardCopy>
                </TextListItem>
              ) : (
                <TextListItem
                  className="pf-u-text-break-word"
                  component={TextListItemVariants.dd}
                >
                  {typeof value === 'function'
                    ? value() === 0
                      ? 0
                      : value() || 'Unavailable'
                    : data?.image?.[value] || 'Unavailable'}
                </TextListItem>
              )}
            </Fragment>
          );
        })
      : null;

  return (
    <TextContent className="pf-u-ml-lg pf-u-mt-md">
      <Grid span={12}>
        <GridItem span={5}>
          <Text component={TextVariants.h2}>
            {imageVersion ? 'Details' : 'Most recent image'}
          </Text>
          <TextList component={TextListVariants.dl}>
            {buildTextList(detailsMapper) || createSkeleton(6)}
          </TextList>
          <Text component={TextVariants.h2}>User information </Text>
          <TextList component={TextListVariants.dl}>
            {buildTextList(userInfoMapper) || createSkeleton(2)}
          </TextList>
        </GridItem>
        <GridItem span={1} />
        <GridItem span={6}>
          <Text component={TextVariants.h2}>Packages </Text>
          <TextList component={TextListVariants.dl}>
            {buildTextList(packageMapper) || createSkeleton(3)}
          </TextList>
          <Text component={TextVariants.h2}>Changes from previous version</Text>
          <TextList component={TextListVariants.dl}>
            {buildTextList(packageDiffMapper) || createSkeleton(3)}
          </TextList>
        </GridItem>
      </Grid>
    </TextContent>
  );
}
Example #7
Source File: ImageDetailTabs.js    From edge-frontend with Apache License 2.0 4 votes vote down vote up
ImageDetailTabs = ({
  imageData,
  openUpdateWizard,
  imageVersion,
  isLoading,
}) => {
  const location = useLocation();
  const history = useHistory();
  const [activeTabKey, setActiveTabkey] = useState(tabs.details);
  const activeTab = imageVersion ? 'imageTab' : 'imageSetTab';

  const keys = [
    'baseURL',
    'imageSetVersion',
    'imageSetTab',
    'imageVersion',
    'imageTab',
    'packagesToggle',
  ];
  const imageUrlMapper = mapUrlToObj(location.pathname, keys);

  const handleTabClick = (_event, tabIndex) => {
    const selectedTab =
      tabIndex === 0 ? 'details' : imageVersion ? 'packages' : 'versions';

    imageUrlMapper[activeTab] = selectedTab;

    history.push(imageUrlMapper.buildUrl());

    setActiveTabkey(tabIndex);
  };

  useEffect(() => {
    imageUrlMapper['imageTab']
      ? setActiveTabkey(tabs[imageUrlMapper['imageTab']])
      : setActiveTabkey(tabs[imageUrlMapper['imageSetTab']]);
  }, [location.pathname]);

  return (
    <>
      {!imageData.isLoading && imageData.hasError ? (
        <EmptyState
          icon="question"
          title="Image not found"
          body="Please check you have the correct link with the correct image Id."
          primaryAction={{
            text: 'Back to Manage Images',
            href: paths['manage-images'],
          }}
          secondaryActions={[]}
        />
      ) : (
        <div className="edge-c-device--detail add-100vh">
          <Tabs
            className="pf-u-ml-md"
            activeKey={activeTabKey}
            onSelect={handleTabClick}
          >
            <Tab
              eventKey={tabs.details}
              title={<TabTitleText>Details</TabTitleText>}
            >
              <ImageDetailTab
                imageData={imageData}
                imageVersion={imageVersion}
              />
            </Tab>
            {isLoading ? (
              <Tab
                title={
                  <TabTitleText>
                    <Skeleton width="75px" />
                  </TabTitleText>
                }
              ></Tab>
            ) : imageVersion ? (
              <Tab
                eventKey={tabs.packages}
                title={<TabTitleText>Packages</TabTitleText>}
              >
                <ImagePackagesTab imageVersion={imageVersion} />
              </Tab>
            ) : (
              <Tab
                eventKey={tabs.versions}
                title={<TabTitleText>Versions</TabTitleText>}
              >
                <ImageVersionTab
                  imageData={imageData}
                  openUpdateWizard={openUpdateWizard}
                />
              </Tab>
            )}
          </Tabs>
        </div>
      )}
    </>
  );
}
Example #8
Source File: GeneralTable.js    From edge-frontend with Apache License 2.0 4 votes vote down vote up
GeneralTable = ({
  apiFilterSort,
  urlParam,
  filters,
  loadTableData,
  tableData,
  columnNames,
  rows,
  toolbarButtons,
  actionResolver,
  areActionsDisabled,
  defaultSort,
  emptyFilterState,
  toggleButton,
  toggleAction,
  toggleState,
  hasCheckbox = false,
  skeletonRowQuantity,
  selectedItems,
  initSelectedItems,
  kebabItems,
  hasModalSubmitted,
  setHasModalSubmitted,
  isUseApi,
}) => {
  const defaultCheckedRows = initSelectedItems ? initSelectedItems : [];
  const [filterValues, setFilterValues] = useState(createFilterValues(filters));
  const [chipsArray, setChipsArray] = useState([]);
  const [sortBy, setSortBy] = useState(defaultSort);
  const [perPage, setPerPage] = useState(20);
  const [page, setPage] = useState(1);
  const [checkedRows, setCheckedRows] = useState(defaultCheckedRows);
  const dispatch = useDispatch();
  const history = useHistory();

  useEffect(() => {
    if (
      //!history.location.search.includes('add_system_modal=true') &&
      !history.location.search.includes('create_image=true') &&
      !history.location.search.includes('update_image=true')
    ) {
      history.push({
        pathname: history.location.pathname,
        search: stateToUrlSearch('has_filters=true', chipsArray.length > 0),
      });
    }

    const query = apiFilterSort
      ? {
          ...filterParams(chipsArray),
          limit: perPage,
          offset: (page - 1) * perPage,
          ...transformSort({
            direction: sortBy.direction,
            name: columns[sortBy.index].type,
          }),
        }
      : null;

    if (query?.status === 'updateAvailable') {
      delete query.status;
      query.update_available = 'true';
    }

    if (isUseApi) {
      loadTableData(query);
      return;
    }

    apiFilterSort && urlParam
      ? loadTableData(dispatch, urlParam, query)
      : apiFilterSort
      ? loadTableData(dispatch, query)
      : null;
  }, [chipsArray, perPage, page, sortBy]);

  useEffect(() => {
    setCheckedRows(defaultCheckedRows);
  }, [hasModalSubmitted]);

  useEffect(() => {
    selectedItems && selectedItems(checkedRows);
    hasModalSubmitted && setHasModalSubmitted(false);
  }, [checkedRows]);

  const { count, isLoading, hasError } = tableData;

  //Used for repos until the api can sort and filter
  const filteredByName = () => {
    const activeFilters = filterValues.filter(
      (filter) =>
        (filter?.type === 'text' && filter?.value !== '') ||
        (filter?.type === 'checkbox' &&
          filter?.value.find((checked) => checked.isChecked))
    );
    const filteredArray = rows.filter((row) => {
      if (activeFilters.length > 0) {
        return activeFilters?.every((filter) => {
          if (filter.type === 'text') {
            return row.noApiSortFilter[
              columnNames.findIndex((row) => row.title === filter.label)
            ]
              .toLowerCase()
              .includes(filter.value.toLowerCase());
          } else if (filter.type === 'checkbox') {
            return filter.value.some(
              (value) =>
                value.isChecked &&
                row.noApiSortFilter[
                  columnNames.findIndex((row) => row.title === filter.label) - 1
                ].toLowerCase() === value.value.toLowerCase()
            );
          }
        });
      } else {
        return row;
      }
    });
    return filteredArray;
  };

  const filteredByNameRows = !apiFilterSort && filteredByName();

  //non-api sort function
  const sortedByDirection = (rows) =>
    rows.sort((a, b) => {
      const index = hasCheckbox ? sortBy.index - 1 : sortBy.index;
      return typeof a?.noApiSortFilter[index] === 'number'
        ? sortBy.direction === 'asc'
          ? a?.noApiSortFilter[index] - b?.noApiSortFilter[index]
          : b?.noApiSortFilter[index] - a?.noApiSortFilter[index]
        : sortBy.direction === 'asc'
        ? a?.noApiSortFilter[index].localeCompare(
            b?.noApiSortFilter[index],
            undefined,
            { sensitivity: 'base' }
          )
        : b?.noApiSortFilter[index].localeCompare(
            a?.noApiSortFilter[index],
            undefined,
            { sensitivity: 'base' }
          );
    });

  const nonApiCount = !apiFilterSort
    ? sortedByDirection(filteredByNameRows)?.length
    : 0;

  const handleSort = (_event, index, direction) => {
    index = hasCheckbox ? index - 1 : index;
    setSortBy({ index, direction });
  };

  const toShowSort =
    isLoading || hasError || (count?.length > 0 && filters.length > 0);

  const columns = columnNames.map((columnName) => ({
    title: columnName.title,
    type: columnName.type,
    transforms: toShowSort ? [] : columnName.sort ? [sortable] : [],
    columnTransforms: columnName.columnTransforms
      ? columnName.columnTransforms
      : [],
  }));

  const filteredRows = apiFilterSort
    ? rows
    : rows.length > 0
    ? sortedByDirection(filteredByNameRows).slice(
        (page - 1) * perPage,
        (page - 1) * perPage + perPage
      )
    : rows;

  const checkboxRows = () =>
    filteredRows.map((row) =>
      checkedRows.some((checkedRow) => checkedRow.id === row.rowInfo.id)
        ? {
            ...row,
            selected: true,
          }
        : {
            ...row,
            selected: false,
          }
    );

  const handleSelect = (_event, isSelecting, rowIndex) => {
    setCheckedRows((prevState) => {
      return isSelecting
        ? [...prevState, { ...filteredRows[rowIndex].rowInfo }]
        : prevState.filter(
            (row) => row.id !== filteredRows[rowIndex].rowInfo.id
          );
    });
  };

  const handlePageSelect = () => {
    setCheckedRows((prevState) => {
      const checkedIds = prevState.map((row) => row.id);
      const rowIsNotIncluded = (id) => !checkedIds.includes(id);

      const newRows = [];
      filteredRows.forEach((filtered) => {
        if (rowIsNotIncluded(filtered.rowInfo.id)) {
          newRows.push({
            ...filtered.rowInfo,
          });
        }
      });

      return [...prevState, ...newRows];
    });
  };

  const handleBulkSelect = () => {
    setCheckedRows(
      rows.map((row) => ({
        ...row.rowInfo,
      }))
    );
  };

  const handleNoneSelect = () => {
    setCheckedRows([]);
  };

  const loadingRows = (perPage) =>
    [...Array(skeletonRowQuantity ?? perPage)].map(() => ({
      cells: columnNames.map(() => ({ title: <Skeleton width="100%" /> })),
    }));

  const emptyFilterView = () => {
    hasCheckbox = false;
    return [
      {
        heightAuto: true,
        cells: [
          {
            props: {
              colSpan: 8,
            },
            title: (
              <CustomEmptyState
                data-testid="general-table-empty-state-no-match"
                bgColor="white"
                icon={emptyFilterState?.icon ?? 'search'}
                title={emptyFilterState?.title ?? 'No match found'}
                body={emptyFilterState?.body ?? ''}
                secondaryActions={
                  toggleAction
                    ? []
                    : [
                        {
                          title: 'Clear all filters',
                          onClick: () =>
                            setFilterValues(createFilterValues(filters)),
                        },
                      ]
                }
              />
            ),
          },
        ],
      },
    ];
  };

  const tableRows = isLoading
    ? loadingRows(perPage)
    : !filteredRows?.length > 0
    ? emptyFilterView()
    : hasCheckbox
    ? checkboxRows()
    : filteredRows;

  return (
    <>
      <ToolbarHeader
        count={apiFilterSort ? count : nonApiCount}
        toolbarButtons={toolbarButtons}
        filters={filters}
        filterValues={filterValues}
        setFilterValues={setFilterValues}
        chipsArray={chipsArray}
        setChipsArray={setChipsArray}
        isLoading={isLoading}
        perPage={perPage}
        setPerPage={setPerPage}
        page={page}
        setPage={setPage}
        toggleButton={toggleButton}
        toggleAction={toggleAction}
        toggleState={toggleState}
        kebabItems={kebabItems}
      >
        {!isLoading && hasCheckbox ? (
          <BulkSelect
            checkedRows={checkedRows}
            handleBulkSelect={handleBulkSelect}
            handlePageSelect={handlePageSelect}
            handleNoneSelect={handleNoneSelect}
            displayedRowsLength={filteredRows.length}
          />
        ) : null}
      </ToolbarHeader>
      <Table
        data-testid="general-table-testid"
        variant="compact"
        aria-label="General Table Component"
        sortBy={hasCheckbox ? { ...sortBy, index: sortBy.index + 1 } : sortBy}
        onSort={handleSort}
        actionResolver={actionResolver ? actionResolver : null}
        areActionsDisabled={areActionsDisabled}
        cells={columns}
        rows={tableRows}
        onSelect={!isLoading && hasCheckbox && handleSelect}
        canSelectAll={false}
      >
        <TableHeader />
        <TableBody />
      </Table>
      <ToolbarFooter
        isLoading={isLoading}
        count={apiFilterSort ? count : nonApiCount}
        setFilterValues={setFilterValues}
        perPage={perPage}
        setPerPage={setPerPage}
        page={page}
        setPage={setPage}
      />
    </>
  );
}