@ant-design/icons#MoreOutlined TypeScript Examples

The following examples show how to use @ant-design/icons#MoreOutlined. 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: personalizationOptions.tsx    From posthog-foss with MIT License 6 votes vote down vote up
IS_TECHNICAL: RadioSelectType[] = [
    {
        key: 'technical',
        label: 'Yes',
        icon: <MoreOutlined />,
    },
    {
        key: 'non_technical',
        label: 'No',
        icon: <EllipsisOutlined />,
    },
]
Example #2
Source File: index.tsx    From RareCamp with Apache License 2.0 5 votes vote down vote up
StyledMoreOutlined = styled(MoreOutlined)`
  cursor: pointer;
  position: absolute;
  left: 30px;
  top: 20px;
`
Example #3
Source File: TitleForShare.tsx    From datart with Apache License 2.0 5 votes vote down vote up
TitleForShare: FC<TitleHeaderProps> = memo(
  ({ name, onShareDownloadData, loadVizData, children }) => {
    const t = useI18NPrefix(`viz.action`);
    const [showTitle, setShowTitle] = useState<boolean>(false);

    if (!showTitle) {
      return (
        <FixedButton onClick={() => setShowTitle(true)}>
          <MoreOutlined />
        </FixedButton>
      );
    }
    return (
      <Wrapper>
        <Space>
          {children}
          <Popconfirm
            placement="left"
            title={t('common.confirm')}
            okText={t('common.ok')}
            cancelText={t('common.cancel')}
            onConfirm={() => {
              onShareDownloadData?.();
            }}
          >
            <Tooltip title={t('share.downloadData')} placement="bottom">
              <DownloadOutlined style={{ fontSize: 20 }} />
            </Tooltip>
          </Popconfirm>
          <Tooltip title={t('syncData')} placement="bottom">
            <ReloadOutlined style={IconStyle} onClick={loadVizData} />
          </Tooltip>
          <CloseOutlined
            onClick={() => setShowTitle(false)}
            style={IconStyle}
          />
        </Space>
      </Wrapper>
    );
  },
)
Example #4
Source File: AppsPage.tsx    From disco-cube-admin with MIT License 5 votes vote down vote up
apps: Record<
  AppNames,
  {
    path: string;
    render: () => React.ReactNode;
    label: string;
    icon: React.ForwardRefExoticComponent<AntdIconProps & React.RefAttributes<HTMLSpanElement>>;
  }
> = {
  rpiDemos: {
    path: `/rpi-demos`,
    label: "RPI Demos",
    icon: BuildOutlined,
    render: () => <ConnectedRpiDemos />,
  },
  video: {
    path: `/video`,
    label: "Video",
    icon: VideoCameraOutlined,
    render: () => <ConnectedVideoApp />,
  },
  cubemap: {
    path: `/cubemap`,
    label: "Cubemap",
    icon: PictureOutlined,
    render: () => <ConnectedCubemapApp />,
  },
  paint: {
    path: `/paint`,
    label: "Paint",
    icon: HighlightOutlined,
    render: () => <ConnectedPaintApp />,
  },
  debug: {
    path: `/debug`,
    label: "Debug",
    icon: BugOutlined,
    render: () => <ConnectedCommonApp appName={`debug`} />,
  },
  sparkle: {
    path: `/sparkle`,
    label: "Sparkle",
    icon: StarOutlined,
    render: () => <ConnectedCommonApp appName={`sparkle`} />,
  },
  sprinkles: {
    path: `/sprinkles`,
    label: "Sprinkles",
    icon: SmallDashOutlined,
    render: () => <ConnectedCommonApp appName={`sprinkles`} />,
  },
  particles: {
    path: `/particles`,
    label: "Particles",
    icon: MoreOutlined,
    render: () => <ConnectedCommonApp appName={`particles`} />,
  },
  particleFlow: {
    path: `/particleFlow`,
    label: "Particle Flow",
    icon: DeploymentUnitOutlined,
    render: () => <ConnectedCommonApp appName={`particleFlow`} />,
  },
  tilt: {
    path: `/tilt`,
    label: "Tilt",
    icon: RotateRightOutlined,
    render: () => <ConnectedCommonApp appName={`tilt`} />,
  },
  maze: {
    path: `/maze`,
    label: "Maze",
    icon: TableOutlined,
    render: () => <ConnectedCommonApp appName={`maze`} />,
  },
}
Example #5
Source File: VizHeader.tsx    From datart with Apache License 2.0 4 votes vote down vote up
VizHeader: FC<{
  chartName?: string;
  status?: number;
  publishLoading?: boolean;
  chartDescription?: string;
  backendChartId?: string;
  onRun?;
  onGotoEdit?;
  onPublish?: () => void;
  onGenerateShareLink?: ({
    expiryDate,
    authenticationMode,
    roles,
    users,
    rowPermissionBy,
  }: {
    expiryDate: string;
    authenticationMode: string;
    roles: string[];
    users: string[];
    rowPermissionBy: string;
  }) => any;
  onDownloadData?;
  onSaveAsVizs?;
  onReloadData?;
  onAddToDashBoard?;
  onRecycleViz?;
  allowDownload?: boolean;
  allowShare?: boolean;
  allowManage?: boolean;
  orgId?: string;
}> = memo(
  ({
    chartName,
    status,
    publishLoading,
    chartDescription,
    onRun,
    onPublish,
    onGotoEdit,
    onGenerateShareLink,
    onDownloadData,
    onSaveAsVizs,
    onReloadData,
    onAddToDashBoard,
    onRecycleViz,
    allowDownload,
    allowShare,
    allowManage,
    orgId,
    backendChartId,
  }) => {
    const t = useI18NPrefix(`viz.action`);
    const [showShareLinkModal, setShowShareLinkModal] = useState(false);
    const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
    const isArchived = Number(status) === 0;

    const handleCloseShareLinkModal = useCallback(() => {
      setShowShareLinkModal(false);
    }, []);

    const handleOpenShareLinkModal = useCallback(() => {
      setShowShareLinkModal(true);
    }, []);

    const handleModalOk = useCallback(
      (dashboardId: string, dashboardType: string) => {
        setIsModalVisible(false);
        onAddToDashBoard?.(dashboardId, dashboardType);
      },
      [onAddToDashBoard],
    );

    const handleModalCancel = useCallback(() => {
      setIsModalVisible(false);
    }, []);

    const getOverlays = () => {
      return (
        <VizOperationMenu
          onShareLinkClick={onGenerateShareLink && handleOpenShareLinkModal}
          onDownloadDataLinkClick={onDownloadData}
          onSaveAsVizs={onSaveAsVizs}
          onReloadData={onReloadData}
          onAddToDashBoard={onAddToDashBoard && setIsModalVisible}
          allowDownload={allowDownload}
          allowShare={allowShare}
          allowManage={allowManage}
          isArchived={isArchived}
          onPublish={Number(status) === 2 ? onPublish : ''}
          onRecycleViz={onRecycleViz}
        />
      );
    };

    const title = useMemo(() => {
      const base = chartName || '';
      const suffix = TITLE_SUFFIX[Number(status)]
        ? `[${t(TITLE_SUFFIX[Number(status)])}]`
        : '';
      return base + suffix;
    }, [chartName, status, t]);

    return (
      <Wrapper>
        <DetailPageHeader
          title={title}
          description={chartDescription}
          disabled={Number(status) < 2}
          actions={
            <>
              {onRun && (
                <Button key="run" icon={<CaretRightFilled />} onClick={onRun}>
                  {t('run')}
                </Button>
              )}
              {allowManage && !isArchived && onPublish && Number(status) === 1 && (
                <Button
                  key="publish"
                  icon={<SendOutlined />}
                  loading={publishLoading}
                  onClick={onPublish}
                >
                  {t('publish')}
                </Button>
              )}
              {allowManage && !isArchived && onGotoEdit && (
                <Button key="edit" icon={<EditOutlined />} onClick={onGotoEdit}>
                  {t('edit')}
                </Button>
              )}
              <Dropdown key="more" arrow overlay={getOverlays()}>
                <Button icon={<MoreOutlined />} />
              </Dropdown>
            </>
          }
        />
        {allowShare && (
          <ShareManageModal
            vizId={backendChartId as string}
            orgId={orgId as string}
            vizType="DATACHART"
            visibility={showShareLinkModal}
            onOk={handleCloseShareLinkModal}
            onCancel={handleCloseShareLinkModal}
            onGenerateShareLink={onGenerateShareLink}
          />
        )}
        {onSaveAsVizs && (
          <SaveToDashboard
            backendChartId={backendChartId}
            title={t('addToDash')}
            orgId={orgId as string}
            isModalVisible={isModalVisible}
            handleOk={handleModalOk}
            handleCancel={handleModalCancel}
          ></SaveToDashboard>
        )}
      </Wrapper>
    );
  },
)
Example #6
Source File: index.tsx    From ant-design-pro-V5-multitab with MIT License 4 votes vote down vote up
Tabs = () => {
    const { initialState } = useModel<any>("@@initialState");
    const { collapsed } = initialState;
    const { tabList, dispatch, active, showTabs, tabsWidth, tabWidth, tarnslateX } = useModel("system");
    const { dropScope, clear } = useAliveController();

    const onSortEnd = ({ oldIndex, newIndex }: { oldIndex: number, newIndex: number }) => {
        const dataSource = JSON.parse(JSON.stringify(tabList))
        const activeItem = dataSource[active as number]
        dataSource.splice(newIndex, 0, dataSource.splice(oldIndex, 1)[0]);
        const movedActiveIndex = dataSource.findIndex((item: string) => item === activeItem)
        dispatch({ type: "CHANGESTATE", payload: { tabList: dataSource, active: movedActiveIndex } })
    }


    // 当前的索引值active showTabs一排能展示多少个
    // 计算应该在菜单展示的菜单有哪些

    const arr: any[] = []
    if (tabList.length > showTabs) {
        // 前面隐藏的元素
        const beforeTab = Math.floor(tarnslateX / tabWidth);
        // 后面隐藏的元素
        const afterTab = Math.floor(tarnslateX / tabWidth) + showTabs
        tabList.forEach((item, index) => {
            if (index < beforeTab) {
                arr.push(item)
            }
            if (index >= afterTab) {
                arr.push(item)
            }
        })
    }


    const menuMore = (
        <Menu onClick={(e) => {
            // 判断点击多余tab的展示移动距离是多少 
            // 计算超出了多少
            // tabsWidth tabWidth showTabs 100是右边操作的距离目前写死
            const isBeyondDistance = ((showTabs as number) * (tabWidth as number)) - (tabsWidth as number) + 100;
            // TODO 找到当前点击的索引值
            const curClickIndex = tabList?.findIndex(item => item.pathname === e.key) as number;
            // 能展示多少个
            const totalShowIndex = (showTabs as number) - 1;
            if (curClickIndex > totalShowIndex) {
                // 计算移动的距离
                const x = (curClickIndex - totalShowIndex) * (tabWidth as number) + isBeyondDistance
                dispatch({ type: 'CHANGESTATE', payload: { tarnslateX: x, active: curClickIndex } })
            } else {
                dispatch({ type: 'CHANGESTATE', payload: { tarnslateX: tabWidth * curClickIndex, active: curClickIndex } })
            }
            history.push({ ...tabList[curClickIndex] })
        }}>
            {
                arr.map(item => {
                    return <Menu.Item key={item.pathname}> {item.title}</Menu.Item>
                })
            }
        </Menu>
    );
    const menu = (
        <Menu onClick={(e) => {
            let activeIndex: number = 0;
            const localTablist = JSON.parse(JSON.stringify(tabList))
            switch (e.key) {
                case "closeCurrent": {
                    const currentName = localTablist[active].keepAliveName
                    if (active > 0) {
                        activeIndex = active - 1
                        const timer = setTimeout(() => {
                            clearTimeout(timer)
                            history.push(tabList[activeIndex])
                        }, 10)
                    } else {
                        activeIndex = 0
                        const timer = setTimeout(() => {
                            clearTimeout(timer)
                            history.push(localTablist[activeIndex])
                        }, 10)
                    }
                    const unlisten = history.listen(() => {
                        unlisten()
                        const dropTimer = setTimeout(() => {
                            clearTimeout(dropTimer)
                            dropScope(currentName)
                        }, 10)
                    })
                    localTablist.splice(active, 1)
                    dispatch({ type: "CHANGESTATE", payload: { tabList: localTablist, active: activeIndex, tarnslateX: 0 } })
                    break;
                }
                case "closeOther": {
                    const needDelete = localTablist.filter((item: any, index: number) => index !== active);
                    const needUpdate = localTablist.filter((item: any, index: number) => index === active);
                    needDelete.forEach((item: any) => dropScope(item.keepAliveName));
                    dispatch({ type: "CHANGESTATE", payload: { tabList: needUpdate, active: 0, tarnslateX: 0 } })
                    break;
                }
                case "closeAll": {
                    const unlisten = history.listen(() => {
                        unlisten()
                        const dropTimer = setTimeout(() => {
                            clearTimeout(dropTimer)
                            clear()
                        }, 10)
                    })
                    const timer = setTimeout(() => {
                        clearTimeout(timer)
                        history.push("/")
                    }, 10)
                    dispatch({ type: "CHANGESTATE", payload: { tabList: [], active: 0, tarnslateX: 0 } })
                    break;
                }
                case "closeLeft": {
                    const needDelete = localTablist.filter((item: any, index: number) => index < active);
                    const needUpdate = localTablist.filter((item: any, index: number) => index >= active);
                    needDelete.forEach((item: any) => dropScope(item.keepAliveName));
                    dispatch({ type: "CHANGESTATE", payload: { tabList: needUpdate, active: 0, tarnslateX: 0 } })
                    break;
                }
                case "closeRight": {
                    const needDelete = localTablist.filter((item: any, index: number) => index > active);
                    const needUpdate = localTablist.filter((item: any, index: number) => index <= active);
                    needDelete.forEach((item: any) => dropScope(item.keepAliveName));
                    dispatch({ type: "CHANGESTATE", payload: { tabList: needUpdate, tarnslateX: 0 } })
                    break;
                }
            }

        }}>
            <Menu.Item key="closeCurrent">关闭当前标签</Menu.Item>
            <Menu.Item key="closeOther">关闭其他标签</Menu.Item>
            <Menu.Item key="closeAll">关闭全部标签</Menu.Item>
            <Menu.Item key="closeLeft" disabled={active === 0}>关闭当前左边标签</Menu.Item>
            <Menu.Item key="closeRight" disabled={active === tabList.length - 1}>关闭当前右边标签</Menu.Item>
        </Menu>
    );



    useEffect(() => {

        window.onresize = () => {
            const width = document.getElementById("contentContainer") ? document.getElementById("contentContainer")!.getBoundingClientRect()!.width : 0;
            dispatch({ type: "CHANGESTATE", payload: { tabsWidth: width, tarnslateX: 0 } })
        }

        const timer = setTimeout(() => {
            const width = document.getElementById("contentContainer") ? document.getElementById("contentContainer")!.getBoundingClientRect()!.width : 0;
            dispatch({ type: "CHANGESTATE", payload: { tabsWidth: width } })
        }, 100);
        return () => {
            clearTimeout(timer)
        }
    }, [collapsed])


    useEffect(() => {
        const timer = setTimeout(() => {
            // 需要重新计算拿到当前tab的宽度
            const tabWidth = document.getElementsByClassName("link-tab")[0] ? document.getElementsByClassName("link-tab")[0]!.getBoundingClientRect().width : 120;
            //计算一排能展示多少个tab 需要减去操作占用的空间100
            const showTabs = Math.ceil((tabsWidth as number - 100) / tabWidth);
            if (tabsWidth > 0 && tabWidth > 0) {
                dispatch({ type: "CHANGESTATE", payload: { showTabs, tabWidth } })
            }
        }, 100);
        return () => {
            clearTimeout(timer)
        }
    }, [tabsWidth])



    return (
        // <div className={styles.tabs} style={{ width: initialState?.collapsed ? "calc(100vw - 41px)" : "calc(100vw - 249px)" }}>
        <div className={styles.tabs} id="contentContainer">
            {tabList.length > 0 && <SortableList onSortEnd={onSortEnd} axis={'x'} distance={1} />}
            <div className={`${styles.tabLeftMenu}  ${tabList.length >= showTabs && styles.boxShadow}`}>
                {
                    tabList.length > showTabs && (
                        <>
                            <Dropdown overlay={menuMore} className={styles.tabMore}>
                                <a className="ant-dropdown-link" onClick={e => e.preventDefault()}>
                                    <MoreOutlined />
                                </a>
                            </Dropdown>
                            <Divider type='vertical' />
                        </>
                    )
                }
                {
                    tabList.length > 1 && (
                        <Dropdown overlay={menu} className={styles.menuRight}>
                            <a className="ant-dropdown-link" onClick={e => e.preventDefault()}>
                                操作 <DownOutlined />
                            </a>
                        </Dropdown>
                    )
                }

            </div>

        </div>
    );
}
Example #7
Source File: Wallets.tsx    From subscan-multisig-react with Apache License 2.0 4 votes vote down vote up
export function Wallets() {
  const { t } = useTranslation();
  const history = useHistory();
  const { api, chain, network } = useApi();
  const [multisigAccounts, setMultisigAccounts] = useState<KeyringAddress[]>([]);
  const isExtensionAccount = useIsInjected();
  const [isCalculating, setIsCalculating] = useState<boolean>(true);
  const [searchKeyword, setSearchKeyword] = useState('');

  const displayMultisigAccounts = useMemo(() => {
    if (!searchKeyword.trim()) {
      return multisigAccounts;
    }
    return multisigAccounts.filter(
      (account) =>
        (account.meta.name && account.meta.name.indexOf(searchKeyword.trim()) >= 0) ||
        account.address.indexOf(searchKeyword.trim()) >= 0
    );
  }, [multisigAccounts, searchKeyword]);

  const { linkColor, mainColor } = useMemo(() => {
    return { linkColor: getLinkColor(network), mainColor: getThemeColor(network) };
  }, [network]);

  const exportAllWallets = () => {
    if (!multisigAccounts || multisigAccounts.length < 1) {
      return;
    }
    const configs: MultisigAccountConfig[] = [];
    multisigAccounts.forEach((multisigAccount) => {
      const config = {
        name: multisigAccount.meta.name || '',
        members: multisigAccount.meta.addressPair as { name: string; address: string }[],
        threshold: multisigAccount.meta.threshold as number,
        scope: getMultiAccountScope(multisigAccount.publicKey),
      };
      configs.push(config);
    });

    const blob = new Blob([JSON.stringify(configs)], { type: 'text/plain;charset=utf-8' });
    saveAs(blob, `multisig_accounts.json`);
  };

  const uploadProps = {
    name: 'file',
    headers: {
      authorization: 'authorization-text',
    },
    onChange(info: any) {
      if (info.file.status !== 'uploading') {
        // console.log(info.file, info.fileList);
      }
      // if (info.file.status === 'done') {
      //   message.success(`${info.file.name} file uploaded successfully`);
      // } else if (info.file.status === 'error') {
      //   message.error(`${info.file.name} file upload failed.`);
      // }
    },
    customRequest(info: any) {
      try {
        const reader = new FileReader();

        reader.onload = (e: any) => {
          // eslint-disable-next-line no-console
          // console.log(e.target.result);

          try {
            const configs = JSON.parse(e.target.result) as MultisigAccountConfig[];
            configs
              .filter((config) => {
                const encodeMembers = config.members.map((member) => {
                  return {
                    name: member.name,
                    address: encodeAddress(member.address, Number(chain.ss58Format)),
                  };
                });
                const publicKey = createKeyMulti(
                  encodeMembers.map((item) => item.address),
                  config.threshold
                );
                const acc = findMultiAccountFromKey(publicKey);
                return !acc;
              })
              .forEach((config) => {
                const encodeMembers = config.members.map((member) => {
                  return {
                    name: member.name,
                    address: encodeAddress(member.address, Number(chain.ss58Format)),
                  };
                });
                const signatories = encodeMembers.map(({ address }) => address);

                const addressPair = config.members.map(({ address, ...other }) => ({
                  ...other,
                  address: encodeAddress(address, Number(chain.ss58Format)),
                }));

                keyring.addMultisig(signatories, config.threshold, {
                  name: config.name,
                  addressPair,
                  genesisHash: api?.genesisHash.toHex(),
                });

                const publicKey = createKeyMulti(
                  encodeMembers.map((item) => item.address),
                  config.threshold
                );

                updateMultiAccountScopeFromKey(publicKey, ShareScope.all, [], network);
              });

            message.success(t('success'));
            refreshMultisigAccounts();
          } catch {
            message.error(t('account config error'));
          }
        };
        reader.readAsText(info.file);
      } catch (err: unknown) {
        if (err instanceof Error) {
          // eslint-disable-next-line no-console
          console.log('err:', err);
        }
      }
    },
  };

  const renderAddress = (address: string) => (
    <Link to={Path.extrinsic + '/' + address + history.location.hash} style={{ color: linkColor }}>
      {address}
    </Link>
  );

  const renderAction = useCallback(
    (row: KeyringAddress) => {
      // const { address } = row;

      return (
        <Space size="middle">
          <div className="flex items-center">
            <Button
              type="primary"
              className="flex items-center justify-center h-7"
              onClick={() => {
                history.push(Path.extrinsic + '/' + row.address + history.location.hash);
              }}
              style={{
                borderRadius: '4px',
              }}
            >
              {t('actions')}
            </Button>

            {(row as unknown as any).entries && (row as unknown as any).entries.length > 0 && (
              <div className="ml-2 bg-red-500 rounded-full w-3 h-3"></div>
            )}
          </div>
        </Space>
      );
    },
    [history, t]
  );

  const columns: ColumnsType<KeyringAddress> = [
    {
      title: t('name'),
      dataIndex: ['meta', 'name'],
    },
    {
      title: t('address'),
      dataIndex: 'address',
      render: renderAddress,
    },
    {
      title: t('balance'),
      key: 'balance',
      render: (account) => {
        return <Space direction="vertical">{renderBalances(account, chain)}</Space>;
      },
    },
    {
      title: t('status.index'),
      key: 'status',
      render: (_, record) => {
        const {
          meta: { addressPair },
        } = record;

        return (addressPair as AddressPair[])?.some((item) => isExtensionAccount(item.address))
          ? t('available')
          : t('watch only');
      },
    },
    {
      // title: t('actions'),
      title: '',
      key: 'action',
      render: (_1: unknown, row) => renderAction(row),
    },
  ];

  const expandedRowRender = (record: KeyringAddress) => {
    const columnsNested: ColumnsType<KeyringJson> = [
      { dataIndex: 'name' },
      {
        dataIndex: 'address',
        render: (address) => (
          <Space size="middle">
            <BaseIdentityIcon theme="polkadot" size={32} value={address} />
            <SubscanLink address={address} />
          </Space>
        ),
      },
      {
        key: 'isInject',
        render: (_, pair) => t(isExtensionAccount(pair.address) ? 'injected' : 'external'),
      },
    ];

    return (
      <div className="multisig-list-expand bg-gray-100 p-5 members">
        <Table
          columns={columnsNested}
          dataSource={record.meta.addressPair as KeyringJson[]}
          pagination={false}
          bordered
          rowKey="address"
          className=" table-without-head"
        />
      </div>
    );
  };

  const refreshMultisigAccounts = useCallback(async () => {
    setIsCalculating(true);

    const accounts = keyring
      .getAccounts()
      .filter((account) => account.meta.isMultisig && isInCurrentScope(account.publicKey, network));
    const balances = await api?.query.system.account.multi(accounts.map(({ address }) => address));
    const entries = await Promise.all(
      accounts.map(async ({ address }) => await api?.query.multisig.multisigs.entries(address))
    );

    setMultisigAccounts(
      accounts.map((item, index) => {
        (item.meta.addressPair as KeyringJson[]).forEach((key) => {
          key.address = convertToSS58(key.address, Number(chain.ss58Format));
        });
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        const source = (balances as any)[index] as unknown as any;
        return {
          ...item,
          value: source.data.free.toString(),
          kton: source.data.freeKton?.toString() ?? '0',
          entries: entries[index] || [],
        };
      })
    );
    setIsCalculating(false);
  }, [api, network, chain]);

  useEffect(() => {
    refreshMultisigAccounts();
  }, [refreshMultisigAccounts]);

  const menu = (
    <Menu>
      <Menu.Item key="1" onClick={exportAllWallets}>
        {t('export all')}
      </Menu.Item>

      <Upload {...uploadProps} showUploadList={false}>
        <Menu.Item key="2">{t('import all')}</Menu.Item>
      </Upload>
    </Menu>
  );

  if (!isCalculating && multisigAccounts.length === 0) {
    return (
      <Space
        direction="vertical"
        className="w-full h-full flex flex-col items-center justify-center absolute"
        id="wallets"
      >
        <div className="flex flex-col items-center">
          <AddIcon className="w-24 h-24" />

          <div className="text-black-800 font-semibold text-xl lg:mt-16 lg:mb-10 mt-6 mb-4">
            Please create Multisig wallet first
          </div>

          <Link to={Path.wallet + history.location.hash}>
            <Button type="primary" className="w-48">
              {t('wallet.add')}
            </Button>
          </Link>

          <div className="my-1">{t('or')}</div>

          <Upload {...uploadProps} showUploadList={false}>
            <Button type="primary" className="w-48">
              {t('import all')}
            </Button>
          </Upload>
        </div>
      </Space>
    );
  }

  return (
    <Space direction="vertical" className="absolute top-4 bottom-4 left-4 right-4 overflow-auto" id="wallets">
      <div className="flex flex-col md:justify-between md:flex-row">
        <div className="flex items-center">
          <Link to={Path.wallet + history.location.hash}>
            <Button type="primary" className="w-44">
              {t('wallet.add')}
            </Button>
          </Link>

          <Dropdown overlay={menu} trigger={['click']} placement="bottomCenter">
            <MoreOutlined
              className="ml-4 rounded-full opacity-60 cursor-pointer p-1"
              style={{
                color: mainColor,
                backgroundColor: mainColor + '40',
              }}
              onClick={(e) => e.preventDefault()}
            />
          </Dropdown>

          {/* {multisigAccounts && multisigAccounts.length >= 1 && (
          <Tooltip title={t('export all')}>
            <ExportIcon className="ml-5 mt-1 w-8 h-8 cursor-pointer" onClick={exportAllWallets} />
          </Tooltip>
        )}

        <Tooltip title={t('import all')}>
          <Upload {...uploadProps} showUploadList={false}>
            <ImportIcon className="ml-5 mt-1 w-8 h-8 cursor-pointer" />
          </Upload>
        </Tooltip> */}
        </div>

        <div className="w-56 mt-4 md:mt-0 md:w-72">
          <Input
            placeholder={t('wallet search placeholder')}
            value={searchKeyword}
            onChange={(e) => {
              setSearchKeyword(e.target.value);
            }}
          />
        </div>
      </div>

      <Table
        columns={columns}
        dataSource={displayMultisigAccounts}
        rowKey="address"
        expandable={{ expandedRowRender, expandIcon: genExpandMembersIcon(t('members')), expandIconColumnIndex: 4 }}
        pagination={false}
        loading={isCalculating}
        className="lg:block hidden multisig-list-table"
      />

      <Space direction="vertical" className="lg:hidden block">
        {displayMultisigAccounts.map((account) => {
          const { address, meta } = account;

          return (
            <Collapse
              key={address}
              expandIcon={() => <></>}
              collapsible={
                (meta.addressPair as AddressPair[])?.some((item) => isExtensionAccount(item.address))
                  ? 'header'
                  : 'disabled'
              }
              className="wallet-collapse"
            >
              <Panel
                header={
                  <Space direction="vertical" className="w-full">
                    <Typography.Text className="mr-4">{meta.name}</Typography.Text>

                    <Typography.Text copyable>{address}</Typography.Text>
                  </Space>
                }
                key={address}
                extra={
                  <Space direction="vertical" className="text-right">
                    <Space>{renderBalances(account, chain)}</Space>
                    {renderAction(account)}
                  </Space>
                }
                className="overflow-hidden mb-4"
              >
                <MemberList data={account} />
              </Panel>
            </Collapse>
          );
        })}
      </Space>
    </Space>
  );
}
Example #8
Source File: StoryHeader.tsx    From datart with Apache License 2.0 4 votes vote down vote up
StoryHeader: FC<StoryHeaderProps> = memo(
  ({
    orgId,
    name,
    toggleEdit,
    status,
    publishLoading,
    playStory,
    onPublish,
    allowShare,
    allowManage,
  }) => {
    const t = useI18NPrefix(`viz.action`);

    const title = useStatusTitle(name, status);

    const isArchived = Number(status) === 0;
    const [showShareLinkModal, setShowShareLinkModal] = useState(false);
    const { storyId: stroyBoardId } = useContext(StoryContext);
    const onOpenShareLink = useCallback(() => {
      setShowShareLinkModal(true);
    }, []);

    const onGenerateShareLink = useCallback(
      async ({
        expiryDate,
        authenticationMode,
        roles,
        users,
        rowPermissionBy,
      }: {
        expiryDate: string;
        authenticationMode: string;
        roles: string[];
        users: string[];
        rowPermissionBy: string;
      }) => {
        const result: any = await generateShareLinkAsync({
          expiryDate,
          authenticationMode,
          roles,
          users,
          rowPermissionBy,
          vizId: stroyBoardId,
          vizType: 'STORYBOARD',
        });
        return result;
      },
      [stroyBoardId],
    );
    return (
      <DetailPageHeader
        title={title}
        disabled={Number(status) < 2}
        actions={
          <>
            {allowManage && !isArchived && Number(status) === 1 && (
              <Button
                key="publish"
                icon={<SendOutlined />}
                loading={publishLoading}
                onClick={onPublish}
              >
                {t('publish')}
              </Button>
            )}
            {allowManage && !isArchived && (
              <Button key="edit" onClick={toggleEdit}>
                {t('edit')}
              </Button>
            )}
            <Button key="run" onClick={playStory}>
              {t('play')}
            </Button>
            {(allowManage || allowShare) && (
              <Dropdown
                overlay={
                  <StoryOverLay
                    allowShare={allowShare}
                    allowManage={allowManage}
                    onOpenShareLink={onOpenShareLink}
                    isArchived={isArchived}
                    onPublish={Number(status) === 2 ? onPublish : ''}
                  />
                }
                arrow
              >
                <Button icon={<MoreOutlined />} />
              </Dropdown>
            )}

            {allowShare && (
              <ShareManageModal
                vizId={stroyBoardId as string}
                orgId={orgId as string}
                vizType="STORYBOARD"
                visibility={showShareLinkModal}
                onOk={() => setShowShareLinkModal(false)}
                onCancel={() => setShowShareLinkModal(false)}
                onGenerateShareLink={onGenerateShareLink}
              />
            )}
          </>
        }
      />
    );
  },
)
Example #9
Source File: List.tsx    From datart with Apache License 2.0 4 votes vote down vote up
List = memo(({ list, selectedId }: StoryboardListProps) => {
  const dispatch = useDispatch();
  const history = useHistory();
  const listLoading = useSelector(selectStoryboardListLoading);
  const orgId = useSelector(selectOrgId);
  const { showSaveForm } = useContext(SaveFormContext);
  const tg = useI18NPrefix('global');

  useEffect(() => {
    dispatch(getStoryboards(orgId));
  }, [dispatch, orgId]);

  const toDetail = useCallback(
    id => () => {
      history.push(`/organizations/${orgId}/vizs/${id}`);
    },
    [history, orgId],
  );

  const redirect = useCallback(
    tabKey => {
      if (tabKey) {
        history.push(`/organizations/${orgId}/vizs/${tabKey}`);
      } else {
        history.push(`/organizations/${orgId}/vizs`);
      }
    },
    [history, orgId],
  );

  const archiveStoryboard = useCallback(
    id => () => {
      dispatch(
        deleteViz({
          params: { id, archive: true },
          type: 'STORYBOARD',
          resolve: () => {
            message.success(tg('operation.archiveSuccess'));
            dispatch(removeTab({ id, resolve: redirect }));
          },
        }),
      );
    },
    [dispatch, redirect, tg],
  );

  const moreMenuClick = useCallback(
    storyboard =>
      ({ key, domEvent }) => {
        domEvent.stopPropagation();
        switch (key) {
          case 'info':
            showSaveForm({
              vizType: 'STORYBOARD',
              type: CommonFormTypes.Edit,
              visible: true,
              initialValues: { id: storyboard.id, name: storyboard.name },
              onSave: (values, onClose) => {
                dispatch(
                  editStoryboard({
                    storyboard: { ...storyboard, ...values },
                    resolve: onClose,
                  }),
                );
              },
            });
            break;
          case 'delete':
            break;
          default:
            break;
        }
      },
    [dispatch, showSaveForm],
  );

  return (
    <Wrapper>
      <ListComponent
        dataSource={list}
        loading={listLoading && { indicator: <LoadingOutlined /> }}
        renderItem={s => (
          <ListItem
            actions={[
              <Access {...allowManageStoryboard(s.id)}>
                <Popup
                  trigger={['click']}
                  placement="bottom"
                  content={
                    <Menu
                      prefixCls="ant-dropdown-menu"
                      selectable={false}
                      onClick={moreMenuClick(s)}
                    >
                      <MenuListItem
                        key="info"
                        prefix={<EditOutlined className="icon" />}
                      >
                        {tg('button.info')}
                      </MenuListItem>
                      <MenuListItem
                        key="delete"
                        prefix={<DeleteOutlined className="icon" />}
                      >
                        <Popconfirm
                          title={tg('operation.archiveConfirm')}
                          onConfirm={archiveStoryboard(s.id)}
                        >
                          {tg('button.archive')}
                        </Popconfirm>
                      </MenuListItem>
                    </Menu>
                  }
                >
                  <Button
                    type="link"
                    icon={<MoreOutlined />}
                    className="btn-hover"
                    onClick={stopPPG}
                  />
                </Popup>
              </Access>,
            ]}
            selected={selectedId === s.id}
            className={classNames({ recycle: true, disabled: s.deleteLoading })}
            onClick={toDetail(s.id)}
          >
            <ListComponent.Item.Meta title={s.name} />
          </ListItem>
        )}
      />
    </Wrapper>
  );
})
Example #10
Source File: Recycle.tsx    From datart with Apache License 2.0 4 votes vote down vote up
Recycle = memo(
  ({ type, orgId, selectedId, list, listLoading, onInit }: RecycleProps) => {
    const dispatch = useDispatch();
    const history = useHistory();
    const { showSaveForm } = useContext(SaveFormContext);
    const vizs = useSelector(selectVizs);
    const isOwner = useSelector(selectIsOrgOwner);
    const permissionMap = useSelector(selectPermissionMap);
    const tg = useI18NPrefix('global');

    useEffect(() => {
      onInit();
    }, [onInit]);

    const redirect = useCallback(
      vizId => {
        if (vizId) {
          history.push(`/organizations/${orgId}/vizs/${vizId}`);
        } else {
          history.push(`/organizations/${orgId}/vizs`);
        }
      },
      [history, orgId],
    );

    const del = useCallback(
      (id, type) => e => {
        e.stopPropagation();
        dispatch(
          deleteViz({
            params: { id, archive: false },
            type,
            resolve: () => {
              message.success(tg('operation.deleteSuccess'));
              dispatch(removeTab({ id, resolve: redirect }));
            },
          }),
        );
      },
      [dispatch, redirect, tg],
    );

    const moreMenuClick = useCallback(
      (id, name, vizType) =>
        ({ key, domEvent }) => {
          domEvent.stopPropagation();
          switch (key) {
            case 'reset':
              showSaveForm({
                vizType,
                type: CommonFormTypes.Edit,
                visible: true,
                initialValues: { id, name, parentId: void 0 },
                onSave: (values, onClose) => {
                  let index = getInsertedNodeIndex(values, vizs);

                  dispatch(
                    unarchiveViz({
                      params: {
                        id,
                        vizType,
                        ...values,
                        parentId: values.parentId || null,
                        index,
                      },
                      resolve: () => {
                        message.success(tg('operation.restoreSuccess'));
                        dispatch(removeTab({ id, resolve: redirect }));
                        onClose();
                      },
                    }),
                  );
                },
              });
              break;
            default:
              break;
          }
        },
      [dispatch, showSaveForm, redirect, vizs, tg],
    );

    const toDetail = useCallback(
      id => () => {
        history.push(`/organizations/${orgId}/vizs/${id}`);
      },
      [history, orgId],
    );

    return (
      <Wrapper>
        <List
          dataSource={list}
          loading={listLoading && { indicator: <LoadingOutlined /> }}
          renderItem={({ id, name, vizType, loading }) => {
            let allowManage = false;
            if (type === 'viz') {
              const viz = vizs.find(v => v.id === id);
              const path = viz
                ? getPath(
                    vizs as Array<{ id: string; parentId: string }>,
                    { id, parentId: viz.parentId },
                    VizResourceSubTypes.Folder,
                  )
                : [id];
              allowManage = getCascadeAccess(
                isOwner,
                permissionMap,
                ResourceTypes.View,
                path,
                PermissionLevels.Manage,
              );
            } else {
              allowManage = !!calcAc(
                isOwner,
                permissionMap,
                ResourceTypes.Viz,
                PermissionLevels.Manage,
                id,
              );
            }
            return (
              <ListItem
                selected={selectedId === id}
                className={classnames({
                  recycle: true,
                  disabled: loading,
                })}
                onClick={toDetail(id)}
                actions={[
                  allowManage && (
                    <Popup
                      trigger={['click']}
                      placement="bottomRight"
                      content={
                        <Menu
                          prefixCls="ant-dropdown-menu"
                          selectable={false}
                          onClick={moreMenuClick(id, name, vizType)}
                        >
                          <MenuListItem
                            key="reset"
                            prefix={<ReloadOutlined className="icon" />}
                          >
                            {tg('button.restore')}
                          </MenuListItem>
                          <MenuListItem
                            key="delelte"
                            prefix={<DeleteOutlined className="icon" />}
                          >
                            <Popconfirm
                              title={tg('operation.deleteConfirm')}
                              onConfirm={del(id, vizType)}
                            >
                              {tg('button.delete')}
                            </Popconfirm>
                          </MenuListItem>
                        </Menu>
                      }
                    >
                      <Button
                        type="link"
                        icon={<MoreOutlined />}
                        className="btn-hover"
                        onClick={stopPPG}
                      />
                    </Popup>
                  ),
                ]}
              >
                <List.Item.Meta title={name} />
              </ListItem>
            );
          }}
        />
      </Wrapper>
    );
  },
)
Example #11
Source File: FolderTree.tsx    From datart with Apache License 2.0 4 votes vote down vote up
export function FolderTree({
  selectedId,
  treeData,
  i18nPrefix,
}: FolderTreeProps) {
  const tg = useI18NPrefix('global');
  const dispatch = useDispatch();
  const history = useHistory();
  const orgId = useSelector(selectOrgId);
  const loading = useSelector(selectVizListLoading);
  const vizsData = useSelector(selectVizs);
  const { showSaveForm } = useContext(SaveFormContext);
  const saveAsViz = useSaveAsViz();

  useEffect(() => {
    dispatch(getFolders(orgId));
  }, [dispatch, orgId]);

  const redirect = useCallback(
    tabKey => {
      if (tabKey) {
        history.push(`/organizations/${orgId}/vizs/${tabKey}`);
      } else {
        history.push(`/organizations/${orgId}/vizs`);
      }
    },
    [history, orgId],
  );

  const menuSelect = useCallback(
    (_, { node }) => {
      if (node.relType !== 'FOLDER') {
        history.push(`/organizations/${orgId}/vizs/${node.relId}`);
      }
    },
    [history, orgId],
  );

  const archiveViz = useCallback(
    ({ id: folderId, relId, relType }) =>
      () => {
        let id = folderId;
        let archive = false;
        let msg = tg('operation.deleteSuccess');

        if (['DASHBOARD', 'DATACHART'].includes(relType)) {
          id = relId;
          archive = true;
          msg = tg('operation.archiveSuccess');
        }
        dispatch(
          deleteViz({
            params: { id, archive },
            type: relType,
            resolve: () => {
              message.success(msg);
              dispatch(removeTab({ id, resolve: redirect }));
            },
          }),
        );
      },
    [dispatch, redirect, tg],
  );

  const moreMenuClick = useCallback(
    node =>
      ({ key, domEvent }) => {
        domEvent.stopPropagation();
        switch (key) {
          case 'info':
            showSaveForm({
              vizType: node.relType,
              type: CommonFormTypes.Edit,
              visible: true,
              initialValues: { ...node, parentId: node.parentId || void 0 },
              onSave: (values, onClose) => {
                let index = node.index;
                if (isParentIdEqual(node.parentId, values.parentId)) {
                  index = getInsertedNodeIndex(values, vizsData);
                }

                dispatch(
                  editFolder({
                    folder: {
                      ...values,
                      parentId: values.parentId || null,
                      index,
                    },
                    resolve: onClose,
                  }),
                );
              },
            });
            break;
          case 'delete':
            break;
          case 'saveAs':
            saveAsViz(node.relId, node.relType);
            break;
          default:
            break;
        }
      },
    [dispatch, showSaveForm, vizsData, saveAsViz],
  );

  const renderTreeTitle = useCallback(
    node => {
      const { isFolder, title, path, relType } = node;

      return (
        <TreeTitle>
          <h4>{`${title}`}</h4>
          <CascadeAccess
            module={ResourceTypes.Viz}
            path={path}
            level={PermissionLevels.Manage}
          >
            <Popup
              trigger={['click']}
              placement="bottom"
              content={
                <Menu
                  prefixCls="ant-dropdown-menu"
                  selectable={false}
                  onClick={moreMenuClick(node)}
                >
                  <MenuListItem
                    key="info"
                    prefix={<EditOutlined className="icon" />}
                  >
                    {tg('button.info')}
                  </MenuListItem>

                  {!isFolder && (
                    <MenuListItem
                      key="saveAs"
                      prefix={<CopyFilled className="icon" />}
                    >
                      {tg('button.saveAs')}
                    </MenuListItem>
                  )}

                  <MenuListItem
                    key="delete"
                    prefix={<DeleteOutlined className="icon" />}
                  >
                    <Popconfirm
                      title={`${
                        relType === 'FOLDER'
                          ? tg('operation.deleteConfirm')
                          : tg('operation.archiveConfirm')
                      }`}
                      onConfirm={archiveViz(node)}
                    >
                      {relType === 'FOLDER'
                        ? tg('button.delete')
                        : tg('button.archive')}
                    </Popconfirm>
                  </MenuListItem>
                </Menu>
              }
            >
              <span className="action" onClick={stopPPG}>
                <MoreOutlined />
              </span>
            </Popup>
          </CascadeAccess>
        </TreeTitle>
      );
    },
    [moreMenuClick, archiveViz, tg],
  );

  const onDrop = info => {
    onDropTreeFn({
      info,
      treeData,
      callback: (id, parentId, index) => {
        dispatch(
          editFolder({
            folder: {
              id,
              parentId,
              index: index,
            },
            resolve: () => {},
          }),
        );
      },
    });
  };
  return (
    <Tree
      loading={loading}
      treeData={treeData}
      titleRender={renderTreeTitle}
      onSelect={menuSelect}
      onDrop={onDrop}
      {...(selectedId && { selectedKeys: [selectedId] })}
      defaultExpandAll
      draggable
    />
  );
}
Example #12
Source File: Recycle.tsx    From datart with Apache License 2.0 4 votes vote down vote up
Recycle = memo(({ list }: RecycleProps) => {
  const dispatch = useDispatch();
  const history = useHistory();
  const { showSaveForm } = useContext(SaveFormContext);
  const loading = useSelector(selectArchivedListLoading);
  const orgId = useSelector(selectOrgId);
  const currentEditingViewKey = useSelector(selectCurrentEditingViewKey);
  const views = useSelector(selectViews);
  const isOwner = useSelector(selectIsOrgOwner);
  const permissionMap = useSelector(selectPermissionMap);
  const t = useI18NPrefix('view.saveForm');
  const tg = useI18NPrefix('global');

  useEffect(() => {
    dispatch(getArchivedViews(orgId));
  }, [dispatch, orgId]);

  const redirect = useCallback(
    currentEditingViewKey => {
      if (currentEditingViewKey) {
        history.push(`/organizations/${orgId}/views/${currentEditingViewKey}`);
      } else {
        history.push(`/organizations/${orgId}/views`);
      }
    },
    [history, orgId],
  );

  const del = useCallback(
    id => e => {
      e.stopPropagation();
      dispatch(
        deleteView({
          id,
          archive: false,
          resolve: () => {
            dispatch(removeEditingView({ id, resolve: redirect }));
            message.success(tg('operation.deleteSuccess'));
          },
        }),
      );
    },
    [dispatch, redirect, tg],
  );

  const moreMenuClick = useCallback(
    (id, name) =>
      ({ key, domEvent }) => {
        domEvent.stopPropagation();
        switch (key) {
          case 'reset':
            showSaveForm({
              type: CommonFormTypes.Edit,
              visible: true,
              simple: true,
              initialValues: { name, parentId: null },
              parentIdLabel: t('folder'),
              onSave: (values, onClose) => {
                let index = getInsertedNodeIndex(values, views);

                dispatch(
                  unarchiveView({
                    view: { ...values, id, index },
                    resolve: () => {
                      dispatch(removeEditingView({ id, resolve: redirect }));
                      message.success(tg('operation.restoreSuccess'));
                      onClose();
                    },
                  }),
                );
              },
            });
            break;
          default:
            break;
        }
      },
    [dispatch, showSaveForm, redirect, views, t, tg],
  );

  const renderTreeTitle = useCallback(
    ({ key, title, parentId }) => {
      const path = views
        ? getPath(
            views as Array<{ id: string; parentId: string }>,
            { id: key, parentId },
            ResourceTypes.View,
          )
        : [];
      const allowManage = getCascadeAccess(
        isOwner,
        permissionMap,
        ResourceTypes.View,
        path,
        PermissionLevels.Manage,
      );
      return (
        <TreeTitle>
          <h4>{`${title}`}</h4>
          {allowManage && (
            <Popup
              trigger={['click']}
              placement="bottomRight"
              content={
                <Menu
                  prefixCls="ant-dropdown-menu"
                  selectable={false}
                  onClick={moreMenuClick(key, title)}
                >
                  <MenuListItem
                    key="reset"
                    prefix={<ReloadOutlined className="icon" />}
                  >
                    {tg('button.restore')}
                  </MenuListItem>
                  <MenuListItem
                    key="delelte"
                    prefix={<DeleteOutlined className="icon" />}
                  >
                    <Popconfirm
                      title={tg('operation.deleteConfirm')}
                      onConfirm={del(key)}
                    >
                      {tg('button.delete')}
                    </Popconfirm>
                  </MenuListItem>
                </Menu>
              }
            >
              <span className="action" onClick={stopPPG}>
                <MoreOutlined />
              </span>
            </Popup>
          )}
        </TreeTitle>
      );
    },
    [moreMenuClick, del, views, isOwner, permissionMap, tg],
  );

  const treeSelect = useCallback(
    (_, { node }) => {
      if (!node.isFolder && node.key !== currentEditingViewKey) {
        history.push(`/organizations/${orgId}/views/${node.key}`);
      }
    },
    [history, orgId, currentEditingViewKey],
  );

  return (
    <Tree
      loading={loading}
      treeData={list}
      titleRender={renderTreeTitle}
      onSelect={treeSelect}
    />
  );
})
Example #13
Source File: FolderTree.tsx    From datart with Apache License 2.0 4 votes vote down vote up
FolderTree = memo(({ treeData }: FolderTreeProps) => {
  const dispatch = useDispatch();
  const history = useHistory();
  const { showSaveForm } = useContext(SaveFormContext);
  const loading = useSelector(selectViewListLoading);
  const currentEditingViewKey = useSelector(selectCurrentEditingViewKey);
  const orgId = useSelector(selectOrgId);
  const viewsData = useSelector(selectViews);
  const isOwner = useSelector(selectIsOrgOwner);
  const permissionMap = useSelector(selectPermissionMap);
  const t = useI18NPrefix('view');
  const tg = useI18NPrefix('global');
  const saveAsView = useSaveAsView();
  const startAnalysis = useStartAnalysis();
  const allowEnableViz = useAccess({
    type: 'module',
    module: ResourceTypes.Viz,
    id: '',
    level: PermissionLevels.Enable,
  })(true);

  useEffect(() => {
    dispatch(getViews(orgId));
  }, [dispatch, orgId]);

  const redirect = useCallback(
    currentEditingViewKey => {
      if (currentEditingViewKey) {
        history.push(`/organizations/${orgId}/views/${currentEditingViewKey}`);
      } else {
        history.push(`/organizations/${orgId}/views`);
      }
    },
    [history, orgId],
  );

  const archive = useCallback(
    (id, isFolder) => e => {
      e.stopPropagation();
      dispatch(
        deleteView({
          id,
          archive: !isFolder,
          resolve: () => {
            dispatch(removeEditingView({ id, resolve: redirect }));
            message.success(
              isFolder
                ? tg('operation.deleteSuccess')
                : tg('operation.archiveSuccess'),
            );
          },
        }),
      );
    },
    [dispatch, redirect, tg],
  );

  const moreMenuClick = useCallback(
    ({ id, name, parentId, index, isFolder }) =>
      ({ key, domEvent }) => {
        domEvent.stopPropagation();
        switch (key) {
          case 'info':
            showSaveForm({
              type: CommonFormTypes.Edit,
              visible: true,
              simple: isFolder,
              initialValues: {
                id,
                name,
                parentId,
              },
              parentIdLabel: t('saveForm.folder'),
              onSave: (values, onClose) => {
                if (isParentIdEqual(parentId, values.parentId)) {
                  index = getInsertedNodeIndex(values, viewsData);
                }

                dispatch(
                  updateViewBase({
                    view: {
                      id,
                      ...values,
                      parentId: values.parentId || null,
                      index,
                    },
                    resolve: onClose,
                  }),
                );
              },
            });
            break;
          case 'delete':
            break;
          case 'saveAs':
            saveAsView(id);
            break;
          case 'startAnalysis':
            startAnalysis(id);
            break;
          default:
            break;
        }
      },
    [dispatch, showSaveForm, viewsData, t, saveAsView, startAnalysis],
  );

  const renderTreeTitle = useCallback(
    node => {
      const { title, path, isFolder, id } = node;
      const isAuthorized = getCascadeAccess(
        isOwner,
        permissionMap,
        ResourceTypes.View,
        path,
        PermissionLevels.Manage,
      );
      return (
        <TreeTitle>
          <h4>{`${title}`}</h4>
          {isAuthorized || allowEnableViz ? (
            <Popup
              trigger={['click']}
              placement="bottom"
              content={
                <Menu
                  prefixCls="ant-dropdown-menu"
                  selectable={false}
                  onClick={moreMenuClick(node)}
                >
                  {isAuthorized && (
                    <MenuListItem
                      key="info"
                      prefix={<EditOutlined className="icon" />}
                    >
                      {tg('button.info')}
                    </MenuListItem>
                  )}

                  {isAuthorized && !isFolder && (
                    <MenuListItem
                      key="saveAs"
                      prefix={<CopyFilled className="icon" />}
                    >
                      {tg('button.saveAs')}
                    </MenuListItem>
                  )}

                  {allowEnableViz && !isFolder && (
                    <MenuListItem
                      prefix={<MonitorOutlined className="icon" />}
                      key="startAnalysis"
                    >
                      {t('editor.startAnalysis')}
                    </MenuListItem>
                  )}

                  {isAuthorized && (
                    <MenuListItem
                      key="delete"
                      prefix={<DeleteOutlined className="icon" />}
                    >
                      <Popconfirm
                        title={
                          isFolder
                            ? tg('operation.deleteConfirm')
                            : tg('operation.archiveConfirm')
                        }
                        onConfirm={archive(id, isFolder)}
                      >
                        {isFolder ? tg('button.delete') : tg('button.archive')}
                      </Popconfirm>
                    </MenuListItem>
                  )}
                </Menu>
              }
            >
              <span className="action" onClick={stopPPG}>
                <MoreOutlined />
              </span>
            </Popup>
          ) : (
            ''
          )}
        </TreeTitle>
      );
    },
    [archive, moreMenuClick, tg, allowEnableViz, t, isOwner, permissionMap],
  );

  const treeSelect = useCallback(
    (_, { node }) => {
      if (!node.isFolder && node.id !== currentEditingViewKey) {
        history.push(`/organizations/${orgId}/views/${node.id}`);
      }
    },
    [history, orgId, currentEditingViewKey],
  );

  const onDrop = info => {
    onDropTreeFn({
      info,
      treeData,
      callback: (id, parentId, index) => {
        dispatch(
          updateViewBase({
            view: {
              id,
              parentId,
              index: index,
              name: info.dragNode.name,
            },
            resolve: () => {},
          }),
        );
      },
    });
  };

  return (
    <Tree
      loading={loading}
      treeData={treeData}
      titleRender={renderTreeTitle}
      selectedKeys={[currentEditingViewKey]}
      onSelect={treeSelect}
      defaultExpandAll
      onDrop={onDrop}
      draggable
    />
  );
})
Example #14
Source File: TitleHeader.tsx    From datart with Apache License 2.0 4 votes vote down vote up
TitleHeader: FC = memo(() => {
  const t = useI18NPrefix(`viz.action`);
  const publishLoading = useSelector(selectPublishLoading);
  const history = useHistory();
  const [showShareLinkModal, setShowShareLinkModal] = useState(false);
  const [showSaveToStory, setShowSaveToStory] = useState<boolean>(false);
  const { name, status, allowManage, allowShare, boardId, orgId } =
    useContext(BoardContext);
  const title = useStatusTitle(name, status);
  const { onGenerateShareLink } = useContext(BoardActionContext);
  const { publishBoard } = usePublishBoard(boardId, 'DASHBOARD', status);
  const onOpenShareLink = useCallback(() => {
    setShowShareLinkModal(true);
  }, []);
  const toBoardEditor = () => {
    const pathName = history.location.pathname;
    if (pathName.includes(boardId)) {
      history.push(`${pathName.split(boardId)[0]}${boardId}/boardEditor`);
    } else if (pathName.includes('/vizs')) {
      history.push(
        `${pathName.split('/vizs')[0]}${'/vizs/'}${boardId}/boardEditor`,
      );
    }
  };

  const saveToStoryOk = useCallback(
    (storyId: string) => {
      history.push({
        pathname: `/organizations/${orgId}/vizs/storyEditor/${storyId}`,
        state: {
          addDashboardId: boardId,
        },
      });
      setShowSaveToStory(false);
    },
    [boardId, history, orgId],
  );

  return (
    <Wrapper>
      <h1 className={classnames({ disabled: status < 2 })}>{title}</h1>
      <Space>
        {allowManage && (
          <>
            {Number(status) === 1 && (
              <Button
                key="publish"
                icon={<SendOutlined />}
                loading={publishLoading}
                onClick={publishBoard}
              >
                {t('publish')}
              </Button>
            )}
            <Button key="edit" icon={<EditOutlined />} onClick={toBoardEditor}>
              {t('edit')}
            </Button>
          </>
        )}
        <Dropdown
          overlay={
            <BoardDropdownList
              onOpenShareLink={onOpenShareLink}
              openStoryList={() => setShowSaveToStory(true)}
            />
          }
          placement="bottomRight"
          arrow
        >
          <Button icon={<MoreOutlined />} />
        </Dropdown>
      </Space>
      {allowManage && (
        <SaveToStoryBoard
          title={t('addToStory')}
          orgId={orgId as string}
          isModalVisible={showSaveToStory}
          handleOk={saveToStoryOk}
          handleCancel={() => setShowSaveToStory(false)}
        ></SaveToStoryBoard>
      )}

      {allowShare && (
        <ShareManageModal
          vizId={boardId as string}
          orgId={orgId as string}
          vizType="DASHBOARD"
          visibility={showShareLinkModal}
          onOk={() => setShowShareLinkModal(false)}
          onCancel={() => setShowShareLinkModal(false)}
          onGenerateShareLink={onGenerateShareLink}
        />
      )}
    </Wrapper>
  );
})
Example #15
Source File: ChartDraggableSourceContainer.tsx    From datart with Apache License 2.0 4 votes vote down vote up
ChartDraggableSourceContainer: FC<
  {
    availableSourceFunctions?: string[];
    onDeleteComputedField?: (fieldName) => void;
    onEditComputedField?: (fieldName) => void;
    onSelectionChange?: (dataItemId, cmdKeyActive, shiftKeyActive) => void;
    onClearCheckedList?: () => void;
  } & ChartDataViewMeta
> = memo(function ChartDraggableSourceContainer({
  id,
  name: colName,
  type,
  subType,
  category,
  expression,
  selectedItems,
  isActive,
  availableSourceFunctions,
  role,
  children,
  onDeleteComputedField,
  onEditComputedField,
  onSelectionChange,
  onClearCheckedList,
}) {
  const t = useI18NPrefix(`viz.workbench.dataview`);
  const [showChild, setShowChild] = useToggle(false);
  const isHierarchyField = role === ColumnRole.Hierarchy;
  const [, drag] = useDrag(
    () => ({
      type: isHierarchyField
        ? CHART_DRAG_ELEMENT_TYPE.DATASET_COLUMN_GROUP
        : CHART_DRAG_ELEMENT_TYPE.DATASET_COLUMN,
      canDrag: true,
      item: selectedItems?.length
        ? selectedItems.map(item => buildDragItem(item))
        : buildDragItem({ id: colName, type, subType, category }, children),
      collect: monitor => ({
        isDragging: monitor.isDragging(),
      }),
      end: onClearCheckedList,
    }),
    [selectedItems],
  );

  const styleClasses: Array<string> = useMemo(() => {
    let styleArr: Array<string> = [];
    if (isActive) {
      styleArr.push('container-active');
    }
    return styleArr;
  }, [isActive]);

  const renderContent = useMemo(() => {
    const _handleMenuClick = (e, fieldName) => {
      if (e.key === 'delete') {
        onDeleteComputedField?.(fieldName);
      } else {
        onEditComputedField?.(fieldName);
      }
    };

    const _isAllowMoreAction = () => {
      return (
        ChartDataViewFieldCategory.Field === category ||
        ChartDataViewFieldCategory.Hierarchy === category
      );
    };

    const _getIconStyle = () => {
      if (role === ColumnRole.Hierarchy) {
        return WARNING;
      }
      if (
        ChartDataViewFieldCategory.ComputedField === category ||
        ChartDataViewFieldCategory.AggregateComputedField === category
      ) {
        return WARNING;
      } else {
        switch (type) {
          case DataViewFieldType.NUMERIC:
            return SUCCESS;
          default:
            return INFO;
        }
      }
    };

    const _getExtraActionMenus = () => {
      return (
        <Menu onClick={e => _handleMenuClick(e, colName)}>
          <Menu.Item key="edit">{t('editField')}</Menu.Item>
          <Menu.Item key="delete">{t('deleteField')}</Menu.Item>
        </Menu>
      );
    };

    let icon = <FileUnknownOutlined />;
    const props = {
      style: {
        alignSelf: 'center',
        color: _getIconStyle(),
      },
    };
    if (role === ColumnRole.Hierarchy) {
      if (!showChild) {
        icon = (
          <FolderAddOutlined
            {...props}
            onClick={() => {
              setShowChild(!showChild);
            }}
          />
        );
      } else {
        icon = (
          <FolderOpenOutlined
            {...props}
            onClick={() => {
              setShowChild(!showChild);
            }}
          />
        );
      }
    } else {
      switch (type) {
        case DataViewFieldType.STRING:
          icon = <FieldStringOutlined {...props} />;
          break;
        case DataViewFieldType.NUMERIC:
          icon = <NumberOutlined {...props} />;
          break;
        case DataViewFieldType.DATE:
          icon = <CalendarOutlined {...props} />;
          break;
        default:
          icon = <FileUnknownOutlined {...props} />;
      }
    }

    return type === 'DATE' && category === 'field' ? (
      <Row align="middle" style={{ width: '100%' }}>
        <CollapseWrapper
          defaultActiveKey={[colName]}
          ghost
          expandIconPosition="right"
          expandIcon={({ isActive }) => {
            return <DownOutlined rotate={isActive ? -180 : 0} />;
          }}
        >
          <Panel
            key={colName}
            header={
              <div ref={drag}>
                <IW fontSize={FONT_SIZE_HEADING}>{icon}</IW>
                <p>{colName}</p>
              </div>
            }
          >
            {DATE_LEVELS.map((item, i) => {
              if (availableSourceFunctions?.includes(item.expression)) {
                return (
                  <DateLevelFieldContainer
                    colName={colName}
                    key={i}
                    item={item}
                    onClearCheckedList={onClearCheckedList}
                  />
                );
              }
              return null;
            })}
          </Panel>
        </CollapseWrapper>
      </Row>
    ) : (
      <Row align="middle" style={{ width: '100%' }}>
        <IW fontSize={FONT_SIZE_HEADING}>{icon}</IW>
        <StyledFieldContent>{colName}</StyledFieldContent>
        <div onClick={stopPPG}>
          <Dropdown
            disabled={_isAllowMoreAction()}
            overlay={_getExtraActionMenus()}
            trigger={['click']}
          >
            <ToolbarButton
              icon={<MoreOutlined />}
              iconSize={FONT_SIZE_BASE}
              className="setting"
              onClick={e => e.preventDefault()}
            />
          </Dropdown>
        </div>
      </Row>
    );
  }, [
    type,
    role,
    colName,
    showChild,
    setShowChild,
    onDeleteComputedField,
    onEditComputedField,
    category,
    t,
    onClearCheckedList,
    drag,
    availableSourceFunctions,
  ]);

  const renderChildren = useMemo(() => {
    return (children || []).map(item => (
      <ChartDraggableSourceContainer
        key={item.id}
        id={item.id}
        name={item.id}
        category={item.category}
        expression={item.expression}
        type={item.type}
        role={item.role}
        children={item.children}
        onDeleteComputedField={onDeleteComputedField}
        onClearCheckedList={onClearCheckedList}
        selectedItems={selectedItems}
      />
    ));
  }, [children, onDeleteComputedField, onClearCheckedList, selectedItems]);

  return (
    <Container
      flexDirection={children ? 'column' : 'row'}
      onClick={e => {
        if (isHierarchyField) {
          return;
        }
        onSelectionChange?.(colName, e.metaKey || e.ctrlKey, e.shiftKey);
      }}
      ref={type === 'DATE' && category === 'field' ? null : drag}
      className={
        type === 'DATE' && category === 'field' ? '' : styleClasses.join(' ')
      }
    >
      {renderContent}
      {showChild && renderChildren}
    </Container>
  );
})
Example #16
Source File: index.tsx    From datart with Apache License 2.0 4 votes vote down vote up
export function ListTitle({
  title,
  subTitle,
  search,
  add,
  more,
  back,
  className,
  onSearch,
  onPrevious,
  onNext,
}: ListTitleProps) {
  const [searchbarVisible, setSearchbarVisible] = useState(false);
  const t = useI18NPrefix('components.listTitle');
  const toggleSearchbar = useCallback(() => {
    setSearchbarVisible(!searchbarVisible);
  }, [searchbarVisible]);

  const moreMenuClick = useCallback(
    ({ key }) => {
      more?.callback(key, onPrevious, onNext);
    },
    [more, onPrevious, onNext],
  );

  const backClick = useCallback(() => {
    onPrevious && onPrevious();
  }, [onPrevious]);

  return (
    <Wrapper className={className}>
      <Title className="title">
        {back && (
          <span className="back" onClick={backClick}>
            <LeftOutlined />
          </span>
        )}
        {title && <h3>{title}</h3>}
        {subTitle && <h5>{subTitle}</h5>}
        <Space size={SPACE_UNIT}>
          {search && (
            <Tooltip title={t('search')} placement="bottom">
              <ToolbarButton
                size="small"
                icon={<SearchOutlined />}
                color={searchbarVisible ? PRIMARY : void 0}
                onClick={toggleSearchbar}
              />
            </Tooltip>
          )}
          {add && <AddButton dataSource={add} />}
          {more && (
            <Popup
              getPopupContainer={triggerNode =>
                triggerNode.parentElement as HTMLElement
              }
              trigger={['click']}
              placement="bottomRight"
              content={
                <MenuWrapper
                  prefixCls="ant-dropdown-menu"
                  selectable={false}
                  onClick={moreMenuClick}
                >
                  {more.items.map(({ key, text, prefix, suffix }) => (
                    <MenuListItem
                      key={key}
                      {...(prefix && { prefix })}
                      {...(suffix && { suffix })}
                    >
                      {text}
                    </MenuListItem>
                  ))}
                </MenuWrapper>
              }
            >
              <ToolbarButton size="small" icon={<MoreOutlined />} />
            </Popup>
          )}
        </Space>
      </Title>
      <Searchbar visible={searchbarVisible}>
        <Input
          className="search-input"
          prefix={<SearchOutlined className="icon" />}
          placeholder={t('searchValue')}
          bordered={false}
          onChange={onSearch}
        />
      </Searchbar>
    </Wrapper>
  );
}
Example #17
Source File: EnterpriseScript.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
EnterpriseScript: (props: any, ref: any) => JSX.Element = (props, ref) => {
  const actionRef = useRef<ActionType>();
  const formRef = useRef({} as any);
  const [keyWord, setKeyWord] = useState('')
  const [groupId, setGroupId] = useState('')
  const [department_id, setDepartmentId] = useState<number[]>([])
  const [allDepartments, setAllDepartments] = useState<DepartmentOption[]>([]);
  const [allDepartmentMap, setAllDepartmentMap] = useState<Dictionary<DepartmentOption>>({});
  const [targetScriptGroup, setTargetScriptGroup] = useState<Partial<ScriptGroup.Item>>({})
  const [groupModalVisible, setGroupModalVisible] = useState(false);
  const groupModalRef = useRef<any>({});
  const scriptModalRef = useRef<any>({});
  const [groupItemsTimestamp, setGroupItemsTimestamp] = useState(Date.now);
  const [scriptModalVisible, setScriptModalVisible] = useState(false);
  const [targetScript, setTargetScript] = useState<Partial<ScriptGroup.Item>>({})
  const [groupItems, setGroupItems] = useState<Partial<ScriptGroup.Item>[]>([]);
  const [allGroupMap, setAllGroupMap] = useState<Dictionary<ScriptGroup.Item>>({});
  const [selectedItems, setSelectedItems] = useState<Script.Item[]>([]);

  useImperativeHandle(ref, () => {
    return {
      createEnterpriseScript: () => {
        setTargetScript({})
        scriptModalRef.current.open({reply_details: [{content_type: 2}]})
      },
    }
  })
  useEffect(() => {
    QueryEnterpriseScriptGroups({page_size: 5000}).then(res => {
      if (res?.code === 0) {
        setGroupItems(res?.data?.items || [])
        setAllGroupMap(_.keyBy<ScriptGroup.Item>(res?.data?.items || [], 'id'));
      }
    }).catch((err) => {
      message.error(err);
    });
  }, [groupItemsTimestamp])

  useEffect(() => {
    QueryDepartmentList({page_size: 5000}).then((res) => {
      if (res?.code === 0) {
        const departments =
          res?.data?.items?.map((item: DepartmentInterface) => {
            return {
              label: item?.name,
              value: item?.ext_id,
              ...item,
            };
          }) || [];
        setAllDepartments(departments);
        setAllDepartmentMap(_.keyBy<DepartmentOption>(departments, 'ext_id'));
      } else {
        message.error(res.message);
      }
    });
  }, []);

  // 后端数据转为前端组件FormItem -- name
  const transferParams = (paramsFromBackEnd: any) => {
    const newReplyDetails = []
    for (let i = 0; i < paramsFromBackEnd.reply_details.length; i += 1) {
      const typeObjectKey = typeEnums[paramsFromBackEnd.reply_details[i].content_type]
      const replyDetailItem: FrontEndReplyDetailParams = {
        content_type: paramsFromBackEnd.reply_details[i].content_type, id: paramsFromBackEnd.reply_details[i].id
      }
      const quickReplyContent = paramsFromBackEnd.reply_details[i].quick_reply_content
      if (typeObjectKey === 'text') {
        replyDetailItem.text_content = quickReplyContent.text.content
      }
      if (typeObjectKey === 'image') {
        replyDetailItem.image_title = quickReplyContent.image.title
        replyDetailItem.image_size = quickReplyContent.image.size
        replyDetailItem.image_picurl = quickReplyContent.image.picurl
      }
      if (typeObjectKey === 'link') {
        replyDetailItem.link_title = quickReplyContent.link.title
        replyDetailItem.link_desc = quickReplyContent.link.desc
        replyDetailItem.link_picurl = quickReplyContent.link.picurl
        replyDetailItem.link_url = quickReplyContent.link.url
      }
      if (typeObjectKey === 'pdf') {
        replyDetailItem.pdf_title = quickReplyContent.pdf.title
        replyDetailItem.pdf_size = quickReplyContent.pdf.size
        replyDetailItem.pdf_fileurl = quickReplyContent.pdf.fileurl
      }
      if (typeObjectKey === 'video') {
        replyDetailItem.video_title = quickReplyContent.video.title
        replyDetailItem.video_size = quickReplyContent.video.size
        replyDetailItem.video_picurl = quickReplyContent.video.picurl
      }
      newReplyDetails.push(replyDetailItem)
    }
    return {...paramsFromBackEnd, reply_details: newReplyDetails}
  }


  const columns: ProColumns<Script.Item>[] = [
    {
      title: '话术内容',
      dataIndex: 'keyword',
      width: '18%',
      hideInSearch: false,
      render: (dom: any, item: any) => {
        return (
          <ScriptContentPreView script={item}/>
        )
      },
    },
    {
      title: '标题',
      dataIndex: 'name',
      key:'name',
      valueType: 'text',
      hideInSearch: true,
    },
    {
      title: '发送次数',
      dataIndex: 'send_count',
      key: 'name',
      valueType: 'digit',
      hideInSearch: true,
      width: 100,
    },
    {
      title: '所属分组',
      width: '14%',
      dataIndex: 'group_id',
      key: 'group_id',
      valueType: 'text',
      hideInSearch: true,
      render: (dom: any) => {
        return (
          <span>{allGroupMap[dom]?.name || '-'}</span>
        )
      },
    },
    {
      title: '创建人',
      dataIndex: 'staff_name',
      key: 'staff_name',
      hideInSearch: true,
      render: (dom, item) => (
        <div className={'tag-like-staff-item'}>
          <img className={'icon'} src={item.avatar}/>
          <span className={'text'}>{dom}</span>
        </div>
      )
    },
    {
      title: '创建时间',
      dataIndex: 'created_at',
      key: 'created_at',
      valueType: 'dateTime',
      hideInSearch: true,
      render: (dom, item) => {
        return (
          <div
            dangerouslySetInnerHTML={{
              __html: moment(item.created_at)
                .format('YYYY-MM-DD HH:mm')
                .split(' ')
                .join('<br />'),
            }}
          />
        );
      },
    },
    {
      title: '类型',
      dataIndex: 'quick_reply_type',
      key: 'quick_reply_type',
      valueType: 'text',
      hideInSearch: true,
      render: (dom: any) => {
        return <span>{typeEnums[dom]}</span>
      }
    },
    {
      title: '可用部门',
      dataIndex: 'department_id',
      key: 'department_id',
      valueType: 'text',
      hideInSearch: false,
      hideInTable: true,
      // eslint-disable-next-line @typescript-eslint/no-unused-vars
      renderFormItem: (schema, config, form) => {
        return (
          <DepartmentTreeSelect
            options={allDepartments}
          />
        )
      },
    },
    {
      title: '操作',
      valueType: 'text',
      width: '10%',
      hideInSearch: true,
      render: (dom) => {
        return (
          <Space>
            <Button
              type={'link'}
              onClick={() => {
                // @ts-ignore
                const target = transferParams(dom)
                setTargetScript(target)
                // @ts-ignore
                scriptModalRef.current.open(target)
              }}
            >修改</Button>
            <Button
              type={'link'}
              onClick={() => {
                Modal.confirm({
                  title: `删除话术`,
                  // @ts-ignore
                  content: `是否确认删除「${dom?.name}」话术?`,
                  okText: '删除',
                  okType: 'danger',
                  cancelText: '取消',
                  onOk() {
                    // @ts-ignore
                    return HandleRequest({ids: [dom?.id]}, DeleteEnterpriseScriptList, () => {
                      actionRef?.current?.reload()
                    })
                  },
                });
              }}
            >
              删除
            </Button>
          </Space>
        )
      }
    },
  ]
  const getUseableDepartment = (departments: number[]) => {
    return departments.map((id) => {
      return <span key={id}>
           {id === 0 ? '全部员工可见' : allDepartmentMap[id]?.label} &nbsp;
        </span>
    })
  }

  return (
    <>
      <ProTable
        onSubmit={() => {
          setKeyWord(formRef.current.getFieldValue('keyword'))
          setDepartmentId(formRef.current.getFieldValue('department_id'))
        }}
        onReset={() => {
          setKeyWord(formRef?.current?.getFieldValue('keyword'))
          setDepartmentId(formRef?.current?.getFieldValue('department_id'))
        }}
        actionRef={actionRef}
        formRef={formRef}
        className={'table'}
        scroll={{x: 'max-content'}}
        columns={columns}
        rowKey={(scriptItem) => scriptItem.id}
        pagination={{
          pageSizeOptions: ['5', '10', '20', '50', '100'],
          pageSize: 5,
        }}
        toolBarRender={false}
        bordered={false}
        tableAlertRender={false}
        rowSelection={{
          onChange: (__, items) => {
            setSelectedItems(items);
          },
        }}
        tableRender={(block, dom) => (
          <div className={styles.mixedTable}>
            <div className={styles.leftPart}>
              <div className={styles.header}>
                <Button
                  key="1"
                  className={styles.button}
                  type="text"
                  onClick={() => {
                    setTargetScriptGroup({})
                    groupModalRef.current.open({})
                  }}
                  icon={<PlusSquareFilled style={{color: 'rgb(154,173,193)', fontSize: 15}}/>}
                >
                  新建分组
                </Button>
              </div>
              <Menu
                onSelect={(e) => {
                  setGroupId(e.key as string)
                }}
                defaultSelectedKeys={['']}
                mode="inline"
                className={styles.menuList}
              >
                <Menu.Item
                  icon={<FolderFilled style={{fontSize: '16px', color: '#138af8'}}/>}
                  onClick={() => setTargetScriptGroup({})}
                  key=""
                >
                  全部
                </Menu.Item>
                {groupItems.map((item) => (
                  <Menu.Item
                    icon={<FolderFilled style={{fontSize: '16px', color: '#138af8'}}/>}
                    key={item.id}
                    onClick={() => {
                      setTargetScriptGroup(item)
                    }}
                  >
                    {item.name}
                    <Dropdown
                      className={'more-actions'}
                      overlay={
                        <Menu
                          onClick={(e) => {
                            e.domEvent.preventDefault();
                            e.domEvent.stopPropagation();
                          }}
                        >
                          <Menu.Item
                            onClick={() => {
                              setTargetScriptGroup(item)
                              groupModalRef.current.open(item)
                            }}
                            key="edit"
                          >
                            <a type={'link'}>修改分组</a>

                          </Menu.Item>
                          <Menu.Item
                            key="delete"
                          >
                            <a
                              type={'link'}
                              onClick={() => {
                                Modal.confirm({
                                  title: `删除分组`,
                                  // @ts-ignore
                                  content: `是否确认删除「${item?.name}」分组?`,
                                  okText: '删除',
                                  okType: 'danger',
                                  cancelText: '取消',
                                  onOk() {
                                    // @ts-ignore
                                    return HandleRequest({ids: [item.id]}, DeleteEnterpriseScriptGroups, () => {
                                      setGroupItemsTimestamp(Date.now)
                                    })
                                  },
                                });
                              }}
                            >
                              删除分组
                            </a>
                          </Menu.Item>
                        </Menu>
                      }
                      trigger={['hover']}
                    >
                      <MoreOutlined style={{color: '#9b9b9b', fontSize: 18}}/>
                    </Dropdown>

                  </Menu.Item>
                ))}

              </Menu>
            </div>
            <div className={styles.rightPart}>
              {
                targetScriptGroup?.id
                &&
                <div className={styles.aboveTableWrap}>
                  可见范围&nbsp;<Tooltip title={'范围内的员工可在企业微信【侧边栏】使用话术'}><QuestionCircleOutlined /></Tooltip>:
                  {allGroupMap[targetScriptGroup.id]?.departments ? getUseableDepartment(allGroupMap[targetScriptGroup.id]?.departments) : '全部员工可见'}
                  &nbsp;&nbsp;&nbsp;
                  <a onClick={() => groupModalRef.current.open(allGroupMap[targetScriptGroup!.id!])}>修改</a>
                </div>
              }
              <div className={styles.tableWrap}>{dom}</div>
            </div>
          </div>
        )}
        params={{
          group_id: groupId || '',
          department_ids: department_id || [],
          keyword: keyWord || ''
        }}
        request={async (params, sort, filter) => {
          return ProTableRequestAdapter(params, sort, filter, QueryEnterpriseScriptList);
        }}
        dateFormatter="string"
      />

      {selectedItems?.length > 0 && (
        // 底部选中条目菜单栏
        <FooterToolbar>
          <span>
            已选择 <a style={{fontWeight: 600}}>{selectedItems.length}</a> 项 &nbsp;&nbsp;
          </span>
          <Divider type='vertical'/>
          <Button
            type='link'
            onClick={() => {
              actionRef.current?.clearSelected?.();
            }}
          >
            取消选择
          </Button>
          <Button
            icon={<DeleteOutlined/>}
            onClick={async () => {
              Modal.confirm({
                title: `删除渠道码`,
                content: `是否批量删除所选「${selectedItems.length}」个渠道码?`,
                okText: '删除',
                okType: 'danger',
                cancelText: '取消',
                onOk() {
                  return HandleRequest(
                    {ids: selectedItems.map((item) => item.id)},
                    DeleteEnterpriseScriptList,
                    () => {
                      actionRef.current?.clearSelected?.();
                      actionRef.current?.reload?.();
                    },
                  );
                },
              });
            }}
            danger={true}
          >
            批量删除
          </Button>
        </FooterToolbar>
      )}
      {/* 新增&修改分组 */}
      <GroupModal
        initialValues={targetScriptGroup}
        allDepartments={allDepartments}
        ref={groupModalRef}
        visible={groupModalVisible}
        setVisible={setGroupModalVisible}
        onFinish={async (values, action) => {
          if (action === 'create') {
            await HandleRequest({...values, sub_groups: []}, CreateEnterpriseScriptGroups, () => {
              setGroupItemsTimestamp(Date.now)
              groupModalRef.current.close()
            })
          } else {
            await HandleRequest({...values, sub_groups: []}, UpdateEnterpriseScriptGroups, () => {
              setGroupItemsTimestamp(Date.now)
              groupModalRef.current.close()
            })
          }
        }}
      />
      {/* 新增&修改话术 */}
      <ScriptModal
        allDepartments={allDepartments}
        initialValues={targetScript}
        ref={scriptModalRef}
        visible={scriptModalVisible}
        setVisible={setScriptModalVisible}
        setPropsGroupsTimestamp={setGroupItemsTimestamp}
        onCancel={() => {
          setGroupItemsTimestamp(Date.now)
        }}
        onFinish={async (values, action) => {
          if (action === 'create') {
            await HandleRequest({...values}, CreateEnterpriseScriptList, () => {
              setGroupItemsTimestamp(Date.now)
              actionRef?.current?.reload()
              scriptModalRef.current.close()
            })
          } else {
            const {reply_details, id, group_id, name, deleted_ids} = values
            await HandleRequest({reply_details, id, group_id, name, deleted_ids}, UpdateEnterpriseScriptList, () => {
              setGroupItemsTimestamp(Date.now)
              actionRef?.current?.reload()
              scriptModalRef.current.close()
            })
          }
        }}
      />
    </>
  )
}
Example #18
Source File: index.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
RoleList: React.FC = () => {
  const [roles, setRoles] = useState<RoleItem[]>([]);
  const [keyword, setKeyword] = useState<string>('');
  const [assignRoleModalVisible, setAssignRoleModalVisible] = useState<boolean>(false);
  const [currentStaff, setCurrentStaff] = useState<StaffInterface>();
  const [currentRole, setCurrentRole] = useState<RoleItem>();
  const [timeStamp, setTimeStamp] = useState<Date>(new Date());
  const actionRef = useRef<ActionType>();
  const [activeTabPaneKey, setActiveTabPaneKey] = useState<string>('staff_list');
  const roleForm = useRef<FormInstance>();

  const extStaffID = (new URLSearchParams(window.location.search)).get('ext_staff_id') || "";

  useEffect(() => {
    Query({page_size: 5000}).then((res) => {
      if (res.code === 0) {
        setRoles(res?.data?.items || []);
      } else {
        message.error(res.message);
      }
    });
  }, [timeStamp]);


  const columns: ProColumns<StaffInterface>[] = [
    {
      title: 'ID',
      dataIndex: 'id',
      valueType: 'text',
      hideInTable: true,
      hideInSearch: true,
    },
    {
      title: '员工',
      dataIndex: 'name',
      valueType: 'text',
      hideInSearch: false,
      render: (dom, item) => {
        return (
          <Space>
            <div className={'tag-like-staff-item'}>
              <img src={item.avatar_url} className={'icon'} alt={item.name}/>
              <span className={'text'}>{item.name}</span>
            </div>
          </Space>
        );
      },
    },
    {
      title: '所在部门',
      dataIndex: 'departments',
      valueType: 'text',
      hideInSearch: true,
      render: (dom) => {
        // @ts-ignore
        const arr = dom?.length > 1 ? dom?.slice(1) : dom;
        return (
          <Space>
            {arr?.map((i: any) => (
              <span key={i.id}>{i.name}</span>
            ))}
          </Space>
        );
      },
    },
    {
      title: '角色',
      dataIndex: 'role_type',
      hideInSearch: false,
      valueType: 'select',
      valueEnum: {
        '': {text: '全部账号', role_type: ''},
        superAdmin: {text: '超级管理员', role_type: 'superAdmin'},
        admin: {text: '管理员', role_type: 'admin'},
        departmentAdmin: {text: '部门管理员', role_type: 'departmentAdmin'},
        staff: {text: '员工', role_type: 'staff'},
      },
    },
    {
      title: '授权状态',
      dataIndex: 'external',
      valueType: 'text',
      hideInSearch: true,
    },
    {
      title: '操作',
      hideInSearch: true,
      width: 100,
      render: (_, item) => {
        return (
          <a
            type={'link'}
            onClick={() => {
              setCurrentStaff(item);
              setAssignRoleModalVisible(true);
            }}>
            更换角色
          </a>
        );
      },
    },
  ];

  // @ts-ignore
  // @ts-ignore
  return (
    <PageContainer
      fixedHeader
      className={styles.roleListContainer}
      extra={[
        <Button
          key='create'
          type='primary'
          icon={<PlusOutlined style={{fontSize: 16, verticalAlign: '-3px'}}/>}
          onClick={() => {
            history.push('/staff-admin/company-management/role/create');
          }}
        >
          添加角色
        </Button>,
      ]}
    >
      <ProCard gutter={8} ghost>
        <ProCard colSpan={{
          md: '240px',
        }} bordered className={styles.leftPart}>
          <div className={styles.header}>
            <Button
              key='1'
              className={styles.button}
              type='text'
              icon={<PlusSquareFilled style={{color: 'rgb(154,173,193)', fontSize: 15}}/>}
              onClick={() => {
                history.push('/staff-admin/company-management/role/create');
              }}
            >
              新建角色
            </Button>
          </div>
          <Menu
            onSelect={() => {
            }}
            defaultSelectedKeys={['0']}
            mode='inline'
            className={styles.menuList}
          >
            <Menu.Item
              key='0'
              onClick={() => {
                setCurrentRole({});
                setActiveTabPaneKey('staff_list');
              }}
            >
              全部
            </Menu.Item>
            {roles.map((item) => (
              <Menu.Item
                key={item.id}
                onClick={() => {
                  setCurrentRole(item);
                }}
              >
                <div className={styles.menuItem}>
                  {item.name}
                  <span className={styles.count}
                        style={{marginRight: item.is_default === True ? 16 : 0}}>{item.count}</span>
                </div>
                {item.is_default === False && (
                  <Dropdown
                    className={'more-actions'}
                    overlay={
                      <Menu
                        onClick={(e) => {
                          e.domEvent.preventDefault();
                          e.domEvent.stopPropagation();
                        }}
                      >
                        <Menu.Item
                          onClick={() => {
                            history.push(`/staff-admin/company-management/role/edit?id=${item.id}`);
                          }}
                          key='edit'
                        >
                          修改
                        </Menu.Item>
                      </Menu>
                    }
                    trigger={['hover']}
                  >
                    <MoreOutlined style={{color: '#9b9b9b', fontSize: 18}}/>
                  </Dropdown>
                )}
              </Menu.Item>
            ))}
          </Menu>
        </ProCard>

        <ProCard bordered className={styles.rightPart}>
          <Tabs
            activeKey={activeTabPaneKey}
            onChange={(key) => {
              setActiveTabPaneKey(key);
            }}
            tabBarExtraContent={{
              right: (
                <>
                  {activeTabPaneKey === 'staff_list' && (
                    <Search
                      placeholder='搜索员工'
                      allowClear={true}
                      onSearch={setKeyword}
                    />
                  )}
                  {activeTabPaneKey === 'permission' && currentRole?.is_default === False && (
                    <Button
                      key='edit'
                      type='dashed'
                      onClick={() => {
                        history.push(`/staff-admin/company-management/role/edit?id=${currentRole?.id}`);
                      }}
                    >
                      修改角色
                    </Button>
                  )}
                </>
              ),
            }}>
            <TabPane className={styles.tabPane} tab='成员列表' key='staff_list'>
              <ProTable
                actionRef={actionRef}
                className={'table'}
                columns={columns}
                rowKey='id'
                pagination={{
                  pageSizeOptions: ['5', '10', '20', '50', '100'],
                  pageSize: 50,
                }}
                toolBarRender={false}
                search={false}
                bordered={true}
                tableAlertRender={false}
                params={{
                  name: keyword,
                  role_id: currentRole?.id,
                  ext_staff_id: extStaffID,
                }}
                request={async (values, sort, filter) => {
                  return ProTableRequestAdapter(values, sort, filter, QueryRoleStaff);
                }}
                dateFormatter='string'
              />
            </TabPane>

            <TabPane className={styles.tabPane} tab='权限范围' key='permission' disabled={!currentRole?.id}>
              <RoleForm
                mode={'simpleEdit'}
                currentItem={currentRole}
                // @ts-ignore
                formRef={roleForm}
                onFinish={async (params) => {
                  const hide = message.loading('修改中');
                  const res: CommonResp = await Update(params);
                  hide();
                  if (res.code === 0) {
                    message.success('修改成功');
                    return true;
                  }

                  if (res.message) {
                    message.error(res.message);
                    return false;
                  }

                  message.error('修改失败');
                  return false;
                }}
              />
            </TabPane>

          </Tabs>
        </ProCard>

      </ProCard>

      <ModalForm
        className={'dialog from-item-label-100w'}
        layout={'horizontal'}
        width={'560px'}
        visible={assignRoleModalVisible}
        onVisibleChange={setAssignRoleModalVisible}
        onFinish={async (params) =>
          HandleRequest({
            ext_staff_ids: [currentStaff?.ext_staff_id],
            role_id: params.role_id,
          }, AssignRoleToStaff, () => {
            actionRef.current?.reload();
            setTimeStamp(new Date());
          })
        }
      >
        <h2 className='dialog-title'> 更换角色 </h2>
        <ProFormSelect
          width={'md'}
          name='role_id'
          label='设置角色'
          // @ts-ignore
          options={roles.map((role) => {
            if (role.type === 'superAdmin') {
              return '';
            }
            return {value: role.id, label: role.name};
          }).filter((role) => role)}
          placeholder='请选择角色'
          rules={[{required: true, message: '请选择角色'}]}
        />
      </ModalForm>

    </PageContainer>
  );
}
Example #19
Source File: index.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
ContactWayList: React.FC = () => {
  const [currentGroup, setCurrentGroup] = useState<ContactWayGroupItem>({});
  const [itemDetailVisible, setItemDetailVisible] = useState(false);
  const [currentItem, setCurrentItem] = useState<ContactWayItem>({});
  const [selectedItems, setSelectedItems] = useState<ContactWayItem[]>([]);
  const [filterGroupID, setFilterGroupID] = useState('0');
  const [groupItems, setGroupItems] = useState<ContactWayGroupItem[]>([]);
  const [groupItemsTimestamp, setGroupItemsTimestamp] = useState(Date.now);
  const [createGroupVisible, setCreateGroupVisible] = useState(false);
  const [batchUpdateVisible, setBatchUpdateVisible] = useState(false);
  const [editGroupVisible, setEditGroupVisible] = useState(false);
  const [allStaffs, setAllStaffs] = useState<StaffOption[]>([]);
  const actionRef = useRef<ActionType>();

  function showDeleteGroupConfirm(item: ContactWayGroupItem) {
    Modal.confirm({
      title: `删除分组`,
      content: `是否确认删除「${item.name}」分组?`,
      // icon: <ExclamationCircleOutlined/>,
      okText: '删除',
      okType: 'danger',
      cancelText: '取消',
      onOk() {
        return HandleRequest({ids: [item.id]}, DeleteGroup, () => {
          setGroupItemsTimestamp(Date.now);
        });
      },
    });
  }

  useEffect(() => {
    QuerySimpleStaffs({page_size: 5000}).then((res) => {
      if (res.code === 0) {
        setAllStaffs(
          res?.data?.items?.map((item: SimpleStaffInterface) => {
            return {
              label: item.name,
              value: item.ext_id,
              ...item,
            };
          }) || [],
        );
      } else {
        message.error(res.message);
      }
    });
  }, []);

  useEffect(() => {
    QueryGroup({page_size: 1000, sort_field: 'sort_weight', sort_type: 'asc'})
      .then((resp) => {
        if (resp && resp.data && resp.data.items) {
          setGroupItems(resp.data.items);
        }
      })
      .catch((err) => {
        message.error(err);
      });
  }, [groupItemsTimestamp]);

  const columns: ProColumns<ContactWayItem>[] = [
    {
      title: 'ID',
      dataIndex: 'id',
      valueType: 'text',
      hideInTable: true,
      hideInSearch: true,
      fixed:'left',
    },
    {
      title: '渠道码',
      dataIndex: 'qr_code',
      valueType: 'image',
      hideInSearch: true,
      width: 80,
      fixed:'left',
      render: (dom, item) => {
        return (
          <div className={'qrcodeWrapper'}>
            <img
              src={item.qr_code}
              onClick={() => {
                setItemDetailVisible(true);
                setCurrentItem(item);
              }}
              className={'qrcode clickable'}
              alt={item.name}
            />
          </div>
        );
      },
    },
    {
      title: '名称',
      dataIndex: 'name',
      valueType: 'text',
      fixed:'left',
    },
    {
      title: '使用员工',
      dataIndex: 'staffs',
      valueType: 'text',
      hideInSearch: true,
      width: 210,
      render: (_, item) => {
        let staffs: any[] = [];
        item.schedules?.forEach((schedule) => {
          if (schedule.staffs) {
            staffs = [...staffs, ...schedule.staffs];
          }
        });
        if (item.schedule_enable === True) {
          staffs = uniqWith(staffs, (a, b) => a.ext_staff_id === b.ext_staff_id);
          return <CollapsedStaffs limit={2} staffs={staffs}/>;
        }
        return <CollapsedStaffs limit={2} staffs={item.staffs}/>;
      },
    },
    {
      title: '使用员工',
      dataIndex: 'ext_staff_ids',
      valueType: 'text',
      hideInTable: true,
      renderFormItem: () => {
        return <StaffTreeSelect options={allStaffs}/>;
      },
    },
    {
      title: '备份员工',
      dataIndex: 'backup_staffs',
      valueType: 'text',
      hideInSearch: true,
      width: 210,
      render: (_, item) => {
        return <CollapsedStaffs limit={2} staffs={item.backup_staffs}/>;
      },
    },
    {
      title: '标签',
      dataIndex: 'customer_tags',
      valueType: 'text',
      ellipsis: true,
      hideInSearch: true,
      width: 210,
      render: (_, item) => {
        return <CollapsedTags limit={3} tags={item.customer_tags}/>;
      },
    },
    {
      title: '添加人次',
      dataIndex: 'add_customer_count',
      valueType: 'digit',
      hideInSearch: true,
      sorter: true,
      showSorterTooltip: false,
      width: 120,
      tooltip: '统计添加渠道码的人次,若客户重复添加将会记录多条数据',
    },
    {
      title: '创建时间',
      dataIndex: 'created_at',
      valueType: 'dateRange',
      sorter: true,
      filtered: true,
      render: (dom, item) => {
        return (
          <div
            dangerouslySetInnerHTML={{
              __html: moment(item.created_at).format('YYYY-MM-DD HH:mm').split(' ').join('<br />'),
            }}
          />
        );
      },
    },
    {
      title: '操作',
      width: 180,
      valueType: 'option',
      render: (_, item) => [
        <a
          key='detail'
          onClick={() => {
            setItemDetailVisible(true);
            setCurrentItem(item);
          }}
        >
          详情
        </a>,
        <a
          key='download'
          onClick={() => {
            if (item?.qr_code) {
              FileSaver.saveAs(item?.qr_code, `${item.name}.png`);
            }
          }}
        >
          下载
        </a>,
        <Dropdown
          key='more'
          overlay={
            <Menu>
              <Menu.Item
                key='edit'
                onClick={() => {
                  history.push(`/staff-admin/customer-growth/contact-way/edit?id=${item.id}`);
                }}
              >
                修改
              </Menu.Item>
              <Menu.Item
                key='copy'
                onClick={() => {
                  history.push(`/staff-admin/customer-growth/contact-way/copy?id=${item.id}`);
                }}
              >
                复制
              </Menu.Item>
              {item.ext_creator_id === localStorage.getItem(LSExtStaffAdminID) && (
                <Menu.Item
                  key='delete'
                  onClick={() => {
                    Modal.confirm({
                      title: `删除渠道码`,
                      content: `是否确认删除「${item.name}」渠道码?`,
                      okText: '删除',
                      okType: 'danger',
                      cancelText: '取消',
                      onOk() {
                        return HandleRequest({ids: [item.id]}, Delete, () => {
                          actionRef.current?.clearSelected?.();
                          actionRef.current?.reload?.();
                        });
                      },
                    });
                  }}
                >删除</Menu.Item>
              )}
            </Menu>
          }
          trigger={['hover']}
        >
          <a style={{display: 'flex', alignItems: 'center'}}>
            编辑
            <CaretDownOutlined style={{fontSize: '8px', marginLeft: '3px'}}/>
          </a>
        </Dropdown>,
      ],
    },
  ];

  // @ts-ignore
  // @ts-ignore
  return (
    <PageContainer
      fixedHeader
      header={{
        title: '渠道活码列表',
        subTitle: (
          <a
            target={'_blank'}
            className={styles.tipsLink}
            // href={'https://www.openscrm.cn/wiki/contact-way'}
          >
            什么是渠道活码?
          </a>
        ),
      }}
      extra={[
        <Button
          key='create'
          type='primary'
          icon={<PlusOutlined style={{fontSize: 16, verticalAlign: '-3px'}}/>}
          onClick={() => {
            history.push('/staff-admin/customer-growth/contact-way/create');
          }}
        >
          新建活码
        </Button>,
      ]}
    >
      <ProTable<ContactWayItem>
        actionRef={actionRef}
        className={'table'}
        scroll={{x: 'max-content'}}
        columns={columns}
        rowKey='id'
        pagination={{
          pageSizeOptions: ['5', '10', '20', '50', '100'],
          pageSize: 5,
        }}
        toolBarRender={false}
        bordered={false}
        tableAlertRender={false}
        rowSelection={{
          onChange: (_, items) => {
            setSelectedItems(items);
          },
        }}
        tableRender={(_, dom) => (
          <div className={styles.mixedTable}>
            <div className={styles.leftPart}>
              <div className={styles.header}>
                <Button
                  key='1'
                  className={styles.button}
                  type='text'
                  onClick={() => setCreateGroupVisible(true)}
                  icon={<PlusSquareFilled style={{color: 'rgb(154,173,193)', fontSize: 15}}/>}
                >
                  新建分组
                </Button>
              </div>
              <Menu
                onSelect={(e) => {
                  setFilterGroupID(e.key as string);
                }}
                defaultSelectedKeys={['0']}
                mode='inline'
                className={styles.menuList}
              >
                <Menu.Item
                  icon={<FolderFilled style={{fontSize: '16px', color: '#138af8'}}/>}
                  key='0'
                >
                  全部
                </Menu.Item>
                {groupItems.map((item) => (
                  <Menu.Item
                    icon={<FolderFilled style={{fontSize: '16px', color: '#138af8'}}/>}
                    key={item.id}
                  >
                    <div className={styles.menuItem}>
                      {item.name}
                      <span className={styles.count}
                            style={{marginRight: item.is_default === True ? 16 : 0}}>{item.count}</span>
                    </div>
                    {item.is_default === False && (
                      <Dropdown
                        className={'more-actions'}
                        overlay={
                          <Menu
                            onClick={(e) => {
                              e.domEvent.preventDefault();
                              e.domEvent.stopPropagation();
                            }}
                          >
                            <Menu.Item
                              onClick={() => {
                                setCurrentGroup(item);
                                setEditGroupVisible(true);
                              }}
                              key='edit'
                            >
                              修改名称
                            </Menu.Item>
                            <Menu.Item
                              onClick={() => {
                                showDeleteGroupConfirm(item);
                              }}
                              key='delete'
                            >
                              删除分组
                            </Menu.Item>
                          </Menu>
                        }
                        trigger={['hover']}
                      >
                        <MoreOutlined style={{color: '#9b9b9b', fontSize: 18}}/>
                      </Dropdown>
                    )}
                  </Menu.Item>
                ))}
              </Menu>
            </div>
            <div className={styles.rightPart}>
              <div className={styles.tableWrap}>{dom}</div>
            </div>
          </div>
        )}
        params={{
          group_id: filterGroupID !== '0' ? filterGroupID : '',
        }}
        request={async (params, sort, filter) => {
          return ProTableRequestAdapter(params, sort, filter, Query);
        }}
        dateFormatter='string'
      />

      {selectedItems?.length > 0 && (
        // 底部选中条目菜单栏
        <FooterToolbar>
          <span>
            已选择 <a style={{fontWeight: 600}}>{selectedItems.length}</a> 项 &nbsp;&nbsp;
            <span></span>
          </span>
          <Divider type='vertical'/>
          <Button
            type='link'
            onClick={() => {
              actionRef.current?.clearSelected?.();
            }}
          >
            取消选择
          </Button>
          <Button onClick={() => setBatchUpdateVisible(true)}>批量分组</Button>
          <Button
            icon={<CloudDownloadOutlined/>}
            type={'primary'}
            onClick={() => {
              Modal.confirm({
                title: `批量下载渠道码`,
                content: `是否批量下载所选「${selectedItems.length}」个渠道码?`,
                okText: '下载',
                cancelText: '取消',
                onOk: async () => {
                  const zip = new JSZip();
                  // eslint-disable-next-line no-restricted-syntax
                  for (const item of selectedItems) {
                    if (item?.qr_code) {
                      // eslint-disable-next-line no-await-in-loop
                      const img = (await fetch(item?.qr_code)).blob();
                      zip.file(`${item.name}_${item.id}.png`, img);
                    }
                  }
                  const content = await zip.generateAsync({type: 'blob'});
                  FileSaver.saveAs(content, `渠道活码_${moment().format('YYYY_MM_DD')}.zip`);
                  actionRef.current?.clearSelected?.();
                  return true;
                },
              });
            }}
          >
            批量下载
          </Button>
          <Button
            icon={<DeleteOutlined/>}
            onClick={async () => {
              Modal.confirm({
                title: `删除渠道码`,
                content: `是否批量删除所选「${selectedItems.length}」个渠道码?`,
                okText: '删除',
                okType: 'danger',
                cancelText: '取消',
                onOk() {
                  return HandleRequest(
                    {ids: selectedItems.map((item) => item.id)},
                    Delete,
                    () => {
                      actionRef.current?.clearSelected?.();
                      actionRef.current?.reload?.();
                    },
                  );
                },
              });
            }}
            danger={true}
          >
            批量删除
          </Button>
        </FooterToolbar>
      )}

      <ModalForm
        width={468}
        className={'dialog from-item-label-100w'}
        layout={'horizontal'}
        visible={batchUpdateVisible}
        onVisibleChange={setBatchUpdateVisible}
        onFinish={async (values) => {
          return await HandleRequest(
            {ids: selectedItems.map((item) => item.id), ...values},
            BatchUpdate,
            () => {
              actionRef.current?.clearSelected?.();
              actionRef.current?.reload?.();
              setGroupItemsTimestamp(Date.now);
            },
          );
        }}
      >
        <h2 className='dialog-title'> 批量修改渠道码 </h2>
        <ProFormSelect
          // @ts-ignore
          options={groupItems.map((groupItem) => {
            return {key: groupItem.id, label: groupItem.name, value: groupItem.id};
          })}
          labelAlign={'left'}
          name='group_id'
          label='新分组'
          placeholder='请选择分组'
          rules={[
            {
              required: true,
              message: '请选择新分组',
            },
          ]}
        />
      </ModalForm>

      <ModalForm
        width={400}
        className={'dialog from-item-label-100w'}
        layout={'horizontal'}
        visible={createGroupVisible}
        onVisibleChange={setCreateGroupVisible}
        onFinish={async (params) =>
          HandleRequest({...currentGroup, ...params}, CreateGroup, () => {
            setGroupItemsTimestamp(Date.now);
          })
        }
      >
        <h2 className='dialog-title'> 新建分组 </h2>
        <ProFormText
          name='name'
          label='分组名称'
          tooltip='最长为 24 个汉字'
          placeholder='请输入分组名称'
          rules={[
            {
              required: true,
              message: '请填写分组名称',
            },
          ]}
        />
      </ModalForm>

      <ModalForm
        className={'dialog from-item-label-100w'}
        layout={'horizontal'}
        width={'500px'}
        visible={editGroupVisible}
        onVisibleChange={setEditGroupVisible}
        onFinish={async (params) =>
          HandleRequest({...currentGroup, ...params}, UpdateGroup, () => {
            setGroupItemsTimestamp(Date.now);
          })
        }
      >
        <h2 className='dialog-title'> 修改名称 </h2>
        <ProFormText
          colon={true}
          name='name'
          label='分组名称'
          tooltip='最长为 24 个汉字'
          placeholder='请输入分组名称'
          initialValue={currentGroup.name}
          rules={[
            {
              required: true,
              message: '请填写分组名称',
            },
          ]}
        />
      </ModalForm>

      <Modal
        className={styles.detailDialog}
        width={'800px'}
        visible={itemDetailVisible}
        onCancel={() => setItemDetailVisible(false)}
        footer={null}
      >
        <h2 className='dialog-title' style={{textAlign: "center", fontSize: 19}}> 渠道码详情 </h2>
        <Row>
          <Col span={8} className={styles.leftPart}>
            <img src={currentItem.qr_code}/>
            <h3>{currentItem.name}</h3>
            <Button
              type={'primary'}
              onClick={() => {
                if (currentItem?.qr_code) {
                  FileSaver.saveAs(currentItem?.qr_code, `${currentItem.name}.png`);
                }
              }}
            >
              下载渠道码
            </Button>
            <Button
              onClick={() => {
                history.push(
                  `/staff-admin/customer-growth/contact-way/edit?id=${currentItem.id}`,
                );
              }}
            >
              修改
            </Button>
          </Col>
          <Col span={16} className={styles.rightPart}>
            <div className={styles.section}>
              <div className={styles.titleWrapper}>
                <div className={styles.divider}/>
                <span className={styles.title}>基本设置</span>
              </div>
              <div className={styles.formItem}>
                <span className={styles.title}>创建时间:</span>
                <span className='date'>
                  {moment(currentItem.created_at).format('YYYY-MM-DD HH:mm')}
                </span>
              </div>
              <div className={styles.formItem}>
                <span className={styles.title}>绑定员工:</span>
                {currentItem.staffs?.map((staff) => (
                  <Tag
                    key={staff.id}
                    className={styles.staffTag}
                    style={{opacity: staff.online === False ? '0.5' : '1'}}
                  >
                    <img className={styles.icon} src={staff.avatar_url} alt={staff.name}/>
                    <span className={styles.text}>{staff.name}</span>
                  </Tag>
                ))}
              </div>
              <div className={styles.formItem}>
                <span className={styles.title}>备份员工:</span>
                {currentItem.backup_staffs?.map((staff) => (
                  <Tag
                    key={staff.id}
                    className={styles.staffTag}
                    style={{opacity: staff.online === False ? '0.5' : '1'}}
                  >
                    <img className={styles.icon} src={staff.avatar_url} alt={staff.name}/>
                    <span className={styles.text}>{staff.name}</span>
                  </Tag>
                ))}
              </div>
              <p className={styles.formItem}>
                <span className={styles.title}>自动通过好友:</span>
                {currentItem.auto_skip_verify_enable === True && (
                  <span>
                    {currentItem.skip_verify_start_time && '~'}
                    {currentItem.skip_verify_end_time}自动通过
                  </span>
                )}
                {currentItem.auto_skip_verify_enable === False && <span>未开启</span>}
              </p>
              <p className={styles.formItem}>
                <span className={styles.title}>客户标签:</span>
                {currentItem.customer_tags?.map((tag) => (
                  <Tag key={tag.id} className={styles.staffTag}>
                    <span className={styles.text}>{tag.name}</span>
                  </Tag>
                ))}
              </p>
            </div>
          </Col>
        </Row>
      </Modal>
    </PageContainer>
  );
}
Example #20
Source File: index.tsx    From jetlinks-ui-antd with MIT License 4 votes vote down vote up
MagnifyComponent: React.FC<Props> = (props) => {

    const [quickInsertVisible, setQuickInsertVisible] = useState<boolean>(props.data.isAssign);
    const [assignVisible, setAssignVisible] = useState<boolean>(props.data.isAssign);
    const [dataList, setDataList] = useState<any[]>([]);
    const [result, setResult] = useState<any[]>([]);
    const [subs, setSubs] = useState<any>();
    const [sub, setSub] = useState<any>();
    const [otherList, setOtherList] = useState<any[]>([]);
    const [isBeginning, setIsBeginning] = useState(true);
    const [editor, setEditor] = useState<any>(null);
    const [script, setScript] = useState(props.data.script || '');
    const [virtualRule, setVirtualRule] = useState(props.data.expands?.virtualRule || {});
    const [virtualId, setVirtualId] = useState('');
    const symbolList = [
        {
            key: 'add',
            value: '+'
        },
        {
            key: 'subtract',
            value: '-'
        },
        {
            key: 'multiply',
            value: '*'
        },
        {
            key: 'divide',
            value: '/'
        }, {
            key: 'parentheses',
            value: '()'
        },
        {
            key: 'cubic',
            value: '^'
        }
    ]
    const otherSymbolList = [
        {
            key: 'dayu',
            value: '>'
        },
        {
            key: 'dayudengyu',
            value: '>='
        },
        {
            key: 'dengyudengyu',
            value: '=='
        },
        {
            key: 'xiaoyudengyu',
            value: '<='
        }, {
            key: 'xiaoyu',
            value: '<'
        },
        {
            key: 'jiankuohao',
            value: '<>'
        },
        {
            key: 'andand',
            value: '&&'
        },
        {
            key: 'huohuo',
            value: '||'
        },
        {
            key: 'fei',
            value: '!'
        },
        {
            key: 'and',
            value: '&'
        },
        {
            key: 'huo',
            value: '|'
        },
        {
            key: 'bolang',
            value: '~'
        }
    ]

    const handleChange = (value: any, record: any) => {
        for (let i in value) {
            record[i] = value[i];
            dataList.map((item) =>
                item.key == record.key ? { ...item, [i]: value[i] } : item)
            setDataList([...dataList])
        }
    }

    const columns = [
        {
            title: '属性ID',
            dataIndex: 'id',
            key: 'id',
            align: 'center',
            render: (text: string, record: any) =>
                <AutoComplete onChange={(value) => {
                    let data: any = otherList.find((item) => {
                        return item.id === value
                    })
                    handleChange({ id: value, type: data?.type }, record);
                }}>
                    {
                        otherList.map(item => <AutoComplete.Option key={item.id}>{item.id}</AutoComplete.Option>)
                    }
                </AutoComplete>
        },
        {
            title: '属性当前值',
            dataIndex: 'current',
            key: 'current',
            render: (text: string, record: any) => <Input value={text} onChange={(e) => {
                handleChange({ current: e.target.value }, record);
            }} />
        },
        {
            title: '属性上一值',
            dataIndex: 'last',
            key: 'last',
            render: (text: string, record: any) => <Input value={text} onChange={(e) => {
                handleChange({ last: e.target.value }, record);
            }} />
        },
        {
            title: '',
            with: 200,
            align: 'center',
            render: (text: string, record: any, index: number) => (
                <span onClick={() => {
                    dataList.splice(index, 1);
                    setDataList([...dataList]);
                }}>
                    <Icon type="delete" />
                </span>
            ),
        }
    ];
    const debugProperty = () => {
        console.log('开始调试...');

        let data: any[] = [];
        dataList.map(item => {
            data.push({
                id: item.id,
                current: item.current,
                last: item.last,
                type: item.type
            })
        })

        if (subs) {
            subs.unsubscribe()
        }
        const ws = getWebsocket(
            `virtual-property-debug-${props.data.id}-${new Date().getTime()}`,
            `/virtual-property-debug`,
            {
                virtualId: virtualId,
                property: props.data.id,
                virtualRule: {
                    ...virtualRule,
                    script: script
                },
                properties: [...data]
            },
        ).subscribe(
            (resp: any) => {
                const { payload } = resp;
                result.push({
                    time: new Date().getTime(),
                    log: JSON.stringify(payload)
                })
                setResult([...result]);
            },
            () => { }
            , () => {
                setIsBeginning(true);
            }
        )
        setSubs(ws);
    };

    const debugPropertyAgain = () => {

        let data: any[] = [];

        dataList.map(item => {
            data.push({
                id: item.id,
                current: item.current,
                last: item.last,
                type: item.type
            })
        })

        if (sub) {
            sub.unsubscribe()
        }
        const ws = getWebsocket(
            `virtual-property-debug-${props.data.id}-${new Date().getTime()}`,
            `/virtual-property-debug`,
            {
                virtualId: virtualId,
                property: props.data.id,
                virtualRule: {
                    ...virtualRule,
                    script: script
                },
                properties: [...data]
            },
        ).subscribe(
            (resp: any) => {
                // console.log(resp);
            }
        );
        setSub(ws);
    };

    const insertContent = (content: string) => {
        const cursorPosition = editor.getCursorPosition();
        editor.session.insert(cursorPosition, content);
    }


    useEffect(() => {
        let formData = props.formData.expands?.virtualRule || {
            windows: []
        };
        let data = props.data.expands?.virtualRule || {};
        let isUseWindow = props.formData?.windows?.includes('useWindow') || false;
        let isTimeWindow = props.formData.windows?.includes('timeWindow') || false;
        if (isUseWindow) {
            setVirtualRule({
                aggType: formData?.aggType || data?.aggType,
                script: formData?.script || data?.script,
                type: 'window',
                window: formData?.window || data?.window,
                windowType: isTimeWindow ? 'time' : 'num'
            })
        } else {
            setVirtualRule({
                type: 'script'
            })
        }

        setVirtualId(`${new Date().getTime()}-virtual-id`)
        if (props.metaDataList.length > 0) {
            let data: any[] = [];
            props.metaDataList.map(item => {
                if (item.id !== props.data.id) {
                    data.push({ id: item.id, type: item.valueType?.type })
                }
            })
            setOtherList([...data]);
        }
    }, []);

    // const editorDidMountHandle = (editor: any, monaco: any) => {
    //     editor.focus();
    // }

    return (
        <Modal
            closable={false}
            visible
            width={quickInsertVisible ? 1200 : 850}
            footer={false}
        >
            <div className={styles.box}>
                <div className={styles.boxLeft} style={{ width: quickInsertVisible ? '70%' : "100%" }}>
                    <div className={styles.header}>
                        <span>设置属性计算规则</span>
                        <div onClick={() => {
                            props.close(script);
                            sub && sub.unsubscribe();
                            subs && subs.unsubscribe();
                        }}><Icon type="fullscreen-exit" /></div>
                    </div>
                    <div className={styles.editorBox} style={{ height: assignVisible ? '400px' : '740px' }}>
                        <div className={styles.editorTop}>
                            <div className={styles.topLeft}>
                                {symbolList.map((item: any, index: number) => {
                                    return <span key={item.key} onClick={() => {
                                        insertContent(symbolList[index].value)
                                    }}>{item.value}</span>
                                })}
                                <span>
                                    <Dropdown overlay={() => (
                                        <Menu>
                                            {
                                                otherSymbolList.map((item, index) => (
                                                    <Menu.Item key={item.key} onClick={() => {
                                                        insertContent(otherSymbolList[index].value)
                                                    }}>{item.value}</Menu.Item>
                                                ))
                                            }
                                        </Menu>
                                    )}>
                                        <a className="ant-dropdown-link" onClick={e => e.preventDefault()}>
                                            <MoreOutlined />
                                        </a>
                                    </Dropdown>
                                </span>
                            </div>
                            <div className={styles.topRight}>
                                <span onClick={() => { setAssignVisible(true) }}><Tooltip title="进行调试"><a>进行调试</a></Tooltip></span>
                                <span onClick={() => { setQuickInsertVisible(true) }}><Tooltip title="快速添加"><a>快速添加</a></Tooltip></span>
                            </div>
                        </div>
                        {/* <MonacoEditor
                            ref={l => setEditor(l && l.editor)}
                            height={assignVisible ? 350 : 690}
                            language='groovy'
                            theme='vs'
                            value={script}
                            options={{
                                selectOnLineNumbers: true
                            }}
                            onChange={(value) => {
                                setScript(value);
                                virtualRule.script = value;
                                setVirtualRule(virtualRule);
                            }}
                            editorDidMount={(editor, monaco) => editorDidMountHandle(editor, monaco)}
                        /> */}
                        <AceEditor
                            ref={l => setEditor(l && l.editor)}
                            mode='groovy'
                            theme="eclipse"
                            name="app_code_editor"
                            key='simulator'
                            fontSize={14}
                            value={script}
                            showPrintMargin
                            showGutter
                            wrapEnabled
                            highlightActiveLine  //突出活动线
                            enableSnippets  //启用代码段
                            style={{ width: '100%', height: assignVisible ? 350 : 690 }}
                            onChange={(value) => {
                                setScript(value);
                                virtualRule.script = value;
                                setVirtualRule(virtualRule);
                            }}
                            setOptions={{
                                enableBasicAutocompletion: true,   //启用基本自动完成功能
                                enableLiveAutocompletion: true,   //启用实时自动完成功能 (比如:智能代码提示)
                                enableSnippets: true,  //启用代码段
                                showLineNumbers: true,
                                tabSize: 2,
                            }}
                        />
                    </div>
                    {assignVisible && <div className={styles.assignBox}>
                        <div className={styles.assignBoxLeft}>
                            <div className={styles.leftHeader}>
                                <div className={styles.itemLeft}>
                                    <div className={styles.itemLeftHeader}>属性赋值</div>
                                    <div className={styles.itemRight}>请对上方规则使用的属性进行赋值</div>
                                </div>
                                {!isBeginning && virtualRule?.type === 'window' && (<div className={styles.item} onClick={() => {
                                    debugPropertyAgain();
                                }}><a>发送数据</a></div>)}
                            </div>
                            <Table rowKey="key" size="middle" columns={columns} dataSource={dataList} pagination={false} scroll={{ y: 195 }}
                                footer={() => <a onClick={() => {
                                    dataList.push({
                                        key: `${new Date().getTime()}`,
                                        id: '',
                                        current: '',
                                        last: '',
                                        type: ''
                                    })
                                    setDataList([...dataList]);
                                }}><Icon type="plus-circle" /> 添加</a>}
                            />
                        </div>
                        <div className={styles.assignBoxRight}>
                            <div className={styles.editorTop}>
                                <div className={styles.topLeft}>
                                    {/* <div>运行详情</div>
                                    <div>错误</div> */}
                                    <div>运行结果</div>
                                </div>
                                <div className={styles.topRight}>
                                    <div>
                                        {isBeginning ? (<a onClick={() => {
                                            setIsBeginning(false);
                                            debugProperty()
                                        }}>开始运行</a>) : (<a onClick={() => {
                                            setIsBeginning(true);
                                            subs && subs.unsubscribe();
                                        }}>停止运行</a>)}
                                    </div>
                                    <div onClick={() => {
                                        setResult([]);
                                    }}><a>清空</a></div>
                                </div>
                            </div>
                            {/* <MonacoEditor
                                height={295}
                                language='groovy'
                                theme='vs'
                                value={result}
                                options={{
                                    selectOnLineNumbers: true,
                                    readOnly: true
                                }}
                                editorDidMount={(editor, monaco) => editorDidMountHandle(editor, monaco)}
                            /> */}
                            <div className={styles.logBox}>
                                <Descriptions>
                                    {result.map((item: any, index: number) => {
                                        return <Descriptions.Item key={index} span={3}
                                            label={moment(item.time).format('HH:mm:ss')}>
                                            <Tooltip placement="top" title={item.log}>{item.log}</Tooltip>
                                        </Descriptions.Item>
                                    })}
                                </Descriptions>
                            </div>
                        </div>
                    </div>}
                </div>
                {quickInsertVisible && <div className={styles.boxRight}>
                    <div className={styles.rightHeader}>
                        <span>快速添加</span>
                        <div onClick={() => { setQuickInsertVisible(false) }}><Icon type="close" /></div>
                    </div>
                    <QuickInsertComponent insertContent={(data: string) => { insertContent(data) }} metaDataList={props.metaDataList} close={() => { }} />
                </div>}
            </div>
        </Modal>
    );
}
Example #21
Source File: index.tsx    From jetlinks-ui-antd with MIT License 4 votes vote down vote up
VirtualEditorComponent: React.FC<Props> = props => {

    const [isMagnify, setIsMagnify] = useState<boolean>(false);
    const [isQuickInsert, setIsQuickInsert] = useState<boolean>(false);
    const [isAssign, setIsAssign] = useState<boolean>(false);
    const [editor, setEditor] = useState<any>(null);
    const [script, setScript] = useState(props.data.expands?.virtualRule?.script || '');
    const symbolList = [
        {
            key: 'add',
            value: '+'
        },
        {
            key: 'subtract',
            value: '-'
        },
        {
            key: 'multiply',
            value: '*'
        },
        {
            key: 'divide',
            value: '/'
        }, {
            key: 'parentheses',
            value: '()'
        },
        {
            key: 'cubic',
            value: '^'
        }
    ]
    const otherSymbolList = [
        {
            key: 'dayu',
            value: '>'
        },
        {
            key: 'dayudengyu',
            value: '>='
        },
        {
            key: 'dengyudengyu',
            value: '=='
        },
        {
            key: 'xiaoyudengyu',
            value: '<='
        }, {
            key: 'xiaoyu',
            value: '<'
        },
        {
            key: 'jiankuohao',
            value: '<>'
        },
        {
            key: 'andand',
            value: '&&'
        },
        {
            key: 'huohuo',
            value: '||'
        },
        {
            key: 'fei',
            value: '!'
        },
        {
            key: 'and',
            value: '&'
        },
        {
            key: 'huo',
            value: '|'
        },
        {
            key: 'bolang',
            value: '~'
        }
    ]
    const insertContent = (content: string) => {
        const cursorPosition = editor.getCursorPosition();
        editor.session.insert(cursorPosition, content);
    }
    return (
        <div className={styles.editorBox}>
            <div className={styles.editorTop}>
                <div className={styles.topLeft}>
                    {symbolList.map((item, index) => (
                        <span key={item.key} onClick={() => {
                            insertContent(symbolList[index].value)
                        }}>{item.value}</span>
                    ))}
                    <span>
                        <Dropdown overlay={() => (
                            <Menu>
                                {
                                    otherSymbolList.map((item, index) => (
                                        <Menu.Item key={item.key} onClick={() => {
                                            insertContent(otherSymbolList[index].value)
                                        }}>{item.value}</Menu.Item>
                                    ))
                                }
                            </Menu>
                        )}>
                            <a className="ant-dropdown-link" onClick={e => e.preventDefault()}>
                                <MoreOutlined />
                            </a>
                        </Dropdown>
                    </span>
                </div>
                <div className={styles.topRight}>
                    <span onClick={() => {
                        setIsAssign(true);
                        setIsMagnify(true);
                    }}><Tooltip title="进行调试"><a>进行调试</a></Tooltip></span>
                    <span onClick={() => {
                        setIsQuickInsert(true);
                    }}><Tooltip title="快速添加"><a>快速添加</a></Tooltip></span>
                    <span onClick={() => {
                        setIsAssign(false);
                        setIsMagnify(true);
                    }}><Icon type="fullscreen" /></span>
                </div>
            </div>
            <AceEditor
                ref={l => setEditor(l && l.editor)}
                mode='groovy'
                theme="eclipse"
                name="app_code_editor"
                key='simulator'
                fontSize={14}
                value={script}
                showPrintMargin
                showGutter
                wrapEnabled
                highlightActiveLine  //突出活动线
                enableSnippets  //启用代码段
                style={{ height: 300 }}
                onChange={(value) => {
                    props.scriptValue(value);
                    setScript(value);
                }}
                setOptions={{
                    enableBasicAutocompletion: true,   //启用基本自动完成功能
                    enableLiveAutocompletion: true,   //启用实时自动完成功能 (比如:智能代码提示)
                    enableSnippets: true,  //启用代码段
                    showLineNumbers: true,
                    tabSize: 2,
                }}
            />
            {/* <MonacoEditor
                // ref={l => setEditor(l && l.editor)}
                height="300"
                language='groovy'
                theme='vs'
                value={'test'}
                options={{
                    selectOnLineNumbers: true
                }}
                // onChange={(value) => {
                //     props.scriptValue(value);
                //     setScript(value);
                // }}
                editorDidMount={(editor, monaco) => editorDidMountHandle(editor, monaco)}
            /> */}
            {isMagnify && (
                <MagnifyComponent formData={
                    props.formData
                }metaDataList={props.metaDataList} data={{
                    isAssign: isAssign,
                    script: script,
                    ...props.data
                }} close={(data: any) => { setIsMagnify(false); setScript(data) }} />
            )}
            <Drawer title="快速添加" visible={isQuickInsert} width={400} onClose={() => { setIsQuickInsert(false); }}>
                <QuickInsertComponent insertContent={(data: string) => { insertContent(data) }} metaDataList={props.metaDataList} close={() => { setIsQuickInsert(false); }} />
            </Drawer>
        </div>
    );
}
Example #22
Source File: Icon.tsx    From html2sketch with MIT License 4 votes vote down vote up
IconSymbol: FC = () => {
  return (
    <Row>
      {/*<CaretUpOutlined*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/1.CaretUpOutlined'}*/}
      {/*/>*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/2.MailOutlined'}*/}
      {/*/>*/}
      {/*<StepBackwardOutlined*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
      {/*/>*/}
      {/*<StepForwardOutlined*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
      {/*/>*/}
      <StepForwardOutlined />
      <ShrinkOutlined />
      <ArrowsAltOutlined />
      <DownOutlined />
      <UpOutlined />
      <LeftOutlined />
      <RightOutlined />
      <CaretUpOutlined />
      <CaretDownOutlined />
      <CaretLeftOutlined />
      <CaretRightOutlined />
      <VerticalAlignTopOutlined />
      <RollbackOutlined />
      <FastBackwardOutlined />
      <FastForwardOutlined />
      <DoubleRightOutlined />
      <DoubleLeftOutlined />
      <VerticalLeftOutlined />
      <VerticalRightOutlined />
      <VerticalAlignMiddleOutlined />
      <VerticalAlignBottomOutlined />
      <ForwardOutlined />
      <BackwardOutlined />
      <EnterOutlined />
      <RetweetOutlined />
      <SwapOutlined />
      <SwapLeftOutlined />
      <SwapRightOutlined />
      <ArrowUpOutlined />
      <ArrowDownOutlined />
      <ArrowLeftOutlined />
      <ArrowRightOutlined />
      <LoginOutlined />
      <LogoutOutlined />
      <MenuFoldOutlined />
      <MenuUnfoldOutlined />
      <BorderBottomOutlined />
      <BorderHorizontalOutlined />
      <BorderInnerOutlined />
      <BorderOuterOutlined />
      <BorderLeftOutlined />
      <BorderRightOutlined />
      <BorderTopOutlined />
      <BorderVerticleOutlined />
      <PicCenterOutlined />
      <PicLeftOutlined />
      <PicRightOutlined />
      <RadiusBottomleftOutlined />
      <RadiusBottomrightOutlined />
      <RadiusUpleftOutlined />
      <RadiusUprightOutlined />
      <FullscreenOutlined />
      <FullscreenExitOutlined />
      <QuestionOutlined />
      <PauseOutlined />
      <MinusOutlined />
      <PauseCircleOutlined />
      <InfoOutlined />
      <CloseOutlined />
      <ExclamationOutlined />
      <CheckOutlined />
      <WarningOutlined />
      <IssuesCloseOutlined />
      <StopOutlined />
      <EditOutlined />
      <CopyOutlined />
      <ScissorOutlined />
      <DeleteOutlined />
      <SnippetsOutlined />
      <DiffOutlined />
      <HighlightOutlined />
      <AlignCenterOutlined />
      <AlignLeftOutlined />
      <AlignRightOutlined />
      <BgColorsOutlined />
      <BoldOutlined />
      <ItalicOutlined />
      <UnderlineOutlined />
      <StrikethroughOutlined />
      <RedoOutlined />
      <UndoOutlined />
      <ZoomInOutlined />
      <ZoomOutOutlined />
      <FontColorsOutlined />
      <FontSizeOutlined />
      <LineHeightOutlined />
      <SortAscendingOutlined />
      <SortDescendingOutlined />
      <DragOutlined />
      <OrderedListOutlined />
      <UnorderedListOutlined />
      <RadiusSettingOutlined />
      <ColumnWidthOutlined />
      <ColumnHeightOutlined />
      <AreaChartOutlined />
      <PieChartOutlined />
      <BarChartOutlined />
      <DotChartOutlined />
      <LineChartOutlined />
      <RadarChartOutlined />
      <HeatMapOutlined />
      <FallOutlined />
      <RiseOutlined />
      <StockOutlined />
      <BoxPlotOutlined />
      <FundOutlined />
      <SlidersOutlined />
      <AndroidOutlined />
      <AppleOutlined />
      <WindowsOutlined />
      <IeOutlined />
      <ChromeOutlined />
      <GithubOutlined />
      <AliwangwangOutlined />
      <DingdingOutlined />
      <WeiboSquareOutlined />
      <WeiboCircleOutlined />
      <TaobaoCircleOutlined />
      <Html5Outlined />
      <WeiboOutlined />
      <TwitterOutlined />
      <WechatOutlined />
      <AlipayCircleOutlined />
      <TaobaoOutlined />
      <SkypeOutlined />
      <FacebookOutlined />
      <CodepenOutlined />
      <CodeSandboxOutlined />
      <AmazonOutlined />
      <GoogleOutlined />
      <AlipayOutlined />
      <AntDesignOutlined />
      <AntCloudOutlined />
      <ZhihuOutlined />
      <SlackOutlined />
      <SlackSquareOutlined />
      <BehanceSquareOutlined />
      <DribbbleOutlined />
      <DribbbleSquareOutlined />
      <InstagramOutlined />
      <YuqueOutlined />
      <AlibabaOutlined />
      <YahooOutlined />
      <RedditOutlined />
      <SketchOutlined />
      <AccountBookOutlined />
      <AlertOutlined />
      <ApartmentOutlined />
      <ApiOutlined />
      <QqOutlined />
      <MediumWorkmarkOutlined />
      <GitlabOutlined />
      <MediumOutlined />
      <GooglePlusOutlined />
      <AppstoreAddOutlined />
      <AppstoreOutlined />
      <AudioOutlined />
      <AudioMutedOutlined />
      <AuditOutlined />
      <BankOutlined />
      <BarcodeOutlined />
      <BarsOutlined />
      <BellOutlined />
      <BlockOutlined />
      <BookOutlined />
      <BorderOutlined />
      <BranchesOutlined />
      <BuildOutlined />
      <BulbOutlined />
      <CalculatorOutlined />
      <CalendarOutlined />
      <CameraOutlined />
      <CarOutlined />
      <CarryOutOutlined />
      <CiCircleOutlined />
      <CiOutlined />
      <CloudOutlined />
      <ClearOutlined />
      <ClusterOutlined />
      <CodeOutlined />
      <CoffeeOutlined />
      <CompassOutlined />
      <CompressOutlined />
      <ContactsOutlined />
      <ContainerOutlined />
      <ControlOutlined />
      <CopyrightCircleOutlined />
      <CopyrightOutlined />
      <CreditCardOutlined />
      <CrownOutlined />
      <CustomerServiceOutlined />
      <DashboardOutlined />
      <DatabaseOutlined />
      <DeleteColumnOutlined />
      <DeleteRowOutlined />
      <DisconnectOutlined />
      <DislikeOutlined />
      <DollarCircleOutlined />
      <DollarOutlined />
      <DownloadOutlined />
      <EllipsisOutlined />
      <EnvironmentOutlined />
      <EuroCircleOutlined />
      <EuroOutlined />
      <ExceptionOutlined />
      <ExpandAltOutlined />
      <ExpandOutlined />
      <ExperimentOutlined />
      <ExportOutlined />
      <EyeOutlined />
      <FieldBinaryOutlined />
      <FieldNumberOutlined />
      <FieldStringOutlined />
      <DesktopOutlined />
      <DingtalkOutlined />
      <FileAddOutlined />
      <FileDoneOutlined />
      <FileExcelOutlined />
      <FileExclamationOutlined />
      <FileOutlined />
      <FileImageOutlined />
      <FileJpgOutlined />
      <FileMarkdownOutlined />
      <FilePdfOutlined />
      <FilePptOutlined />
      <FileProtectOutlined />
      <FileSearchOutlined />
      <FileSyncOutlined />
      <FileTextOutlined />
      <FileUnknownOutlined />
      <FileWordOutlined />
      <FilterOutlined />
      <FireOutlined />
      <FlagOutlined />
      <FolderAddOutlined />
      <FolderOutlined />
      <FolderOpenOutlined />
      <ForkOutlined />
      <FormatPainterOutlined />
      <FrownOutlined />
      <FunctionOutlined />
      <FunnelPlotOutlined />
      <GatewayOutlined />
      <GifOutlined />
      <GiftOutlined />
      <GlobalOutlined />
      <GoldOutlined />
      <GroupOutlined />
      <HddOutlined />
      <HeartOutlined />
      <HistoryOutlined />
      <HomeOutlined />
      <HourglassOutlined />
      <IdcardOutlined />
      <ImportOutlined />
      <InboxOutlined />
      <InsertRowAboveOutlined />
      <InsertRowBelowOutlined />
      <InsertRowLeftOutlined />
      <InsertRowRightOutlined />
      <InsuranceOutlined />
      <InteractionOutlined />
      <KeyOutlined />
      <LaptopOutlined />
      <LayoutOutlined />
      <LikeOutlined />
      <LineOutlined />
      <LinkOutlined />
      <Loading3QuartersOutlined />
      <LoadingOutlined />
      <LockOutlined />
      <MailOutlined />
      <ManOutlined />
      <MedicineBoxOutlined />
      <MehOutlined />
      <MenuOutlined />
      <MergeCellsOutlined />
      <MessageOutlined />
      <MobileOutlined />
      <MoneyCollectOutlined />
      <MonitorOutlined />
      <MoreOutlined />
      <NodeCollapseOutlined />
      <NodeExpandOutlined />
      <NodeIndexOutlined />
      <NotificationOutlined />
      <NumberOutlined />
      <PaperClipOutlined />
      <PartitionOutlined />
      <PayCircleOutlined />
      <PercentageOutlined />
      <PhoneOutlined />
      <PictureOutlined />
      <PoundCircleOutlined />
      <PoundOutlined />
      <PoweroffOutlined />
      <PrinterOutlined />
      <ProfileOutlined />
      <ProjectOutlined />
      <PropertySafetyOutlined />
      <PullRequestOutlined />
      <PushpinOutlined />
      <QrcodeOutlined />
      <ReadOutlined />
      <ReconciliationOutlined />
      <RedEnvelopeOutlined />
      <ReloadOutlined />
      <RestOutlined />
      <RobotOutlined />
      <RocketOutlined />
      <SafetyCertificateOutlined />
      <SafetyOutlined />
      <ScanOutlined />
      <ScheduleOutlined />
      <SearchOutlined />
      <SecurityScanOutlined />
      <SelectOutlined />
      <SendOutlined />
      <SettingOutlined />
      <ShakeOutlined />
      <ShareAltOutlined />
      <ShopOutlined />
      <ShoppingCartOutlined />
      <ShoppingOutlined />
      <SisternodeOutlined />
      <SkinOutlined />
      <SmileOutlined />
      <SolutionOutlined />
      <SoundOutlined />
      <SplitCellsOutlined />
      <StarOutlined />
      <SubnodeOutlined />
      <SyncOutlined />
      <TableOutlined />
      <TabletOutlined />
      <TagOutlined />
      <TagsOutlined />
      <TeamOutlined />
      <ThunderboltOutlined />
      <ToTopOutlined />
      <ToolOutlined />
      <TrademarkCircleOutlined />
      <TrademarkOutlined />
      <TransactionOutlined />
      <TrophyOutlined />
      <UngroupOutlined />
      <UnlockOutlined />
      <UploadOutlined />
      <UsbOutlined />
      <UserAddOutlined />
      <UserDeleteOutlined />
      <UserOutlined />
      <UserSwitchOutlined />
      <UsergroupAddOutlined />
      <UsergroupDeleteOutlined />
      <VideoCameraOutlined />
      <WalletOutlined />
      <WifiOutlined />
      <BorderlessTableOutlined />
      <WomanOutlined />
      <BehanceOutlined />
      <DropboxOutlined />
      <DeploymentUnitOutlined />
      <UpCircleOutlined />
      <DownCircleOutlined />
      <LeftCircleOutlined />
      <RightCircleOutlined />
      <UpSquareOutlined />
      <DownSquareOutlined />
      <LeftSquareOutlined />
      <RightSquareOutlined />
      <PlayCircleOutlined />
      <QuestionCircleOutlined />
      <PlusCircleOutlined />
      <PlusSquareOutlined />
      <MinusSquareOutlined />
      <MinusCircleOutlined />
      <InfoCircleOutlined />
      <ExclamationCircleOutlined />
      <CloseCircleOutlined />
      <CloseSquareOutlined />
      <CheckCircleOutlined />
      <CheckSquareOutlined />
      <ClockCircleOutlined />
      <FormOutlined />
      <DashOutlined />
      <SmallDashOutlined />
      <YoutubeOutlined />
      <CodepenCircleOutlined />
      <AliyunOutlined />
      <PlusOutlined />
      <LinkedinOutlined />
      <AimOutlined />
      <BugOutlined />
      <CloudDownloadOutlined />
      <CloudServerOutlined />
      <CloudSyncOutlined />
      <CloudUploadOutlined />
      <CommentOutlined />
      <ConsoleSqlOutlined />
      <EyeInvisibleOutlined />
      <FileGifOutlined />
      <DeliveredProcedureOutlined />
      <FieldTimeOutlined />
      <FileZipOutlined />
      <FolderViewOutlined />
      <FundProjectionScreenOutlined />
      <FundViewOutlined />
      <MacCommandOutlined />
      <PlaySquareOutlined />
      <OneToOneOutlined />
      <RotateLeftOutlined />
      <RotateRightOutlined />
      <SaveOutlined />
      <SwitcherOutlined />
      <TranslationOutlined />
      <VerifiedOutlined />
      <VideoCameraAddOutlined />
      <WhatsAppOutlined />

      {/*</Col>*/}
    </Row>
  );
}
Example #23
Source File: index.tsx    From RareCamp with Apache License 2.0 4 votes vote down vote up
export default function EditProject({
  project,
  onDeleted,
}: {
  project: any
  onDeleted?: any
}) {
  const [isEditProjectVisible, setIsEditProjectVisible] =
    useState(false)
  const deleteProject = () =>
    confirm({
      okButtonProps: {
        style: { backgroundColor: '#e53935', borderColor: '#e53935' },
      },
      title: 'Are you sure you want to delete this project?',
      centered: true,
      icon: <ExclamationCircleOutlined />,
      content:
        'Project will be immediately deleted. You cannot undo this action.',
      okText: 'Delete',
      onOk: deleteProjectMutation.mutateAsync,
      onCancel: () => {},
    })
  const menu = (
    <Menu>
      <Menu.Item>
        <Button
          type="text"
          onClick={() => setIsEditProjectVisible(true)}
        >
          Edit Project name
        </Button>
      </Menu.Item>
      <Menu.Item>
        <Button onClick={deleteProject} type="text">
          <Text type="danger">Delete Project</Text>
        </Button>
      </Menu.Item>
    </Menu>
  )
  const queryClient = useQueryClient()
  const deleteProjectMutation = useMutation(
    () =>
      axios.delete(
        `/programs/${project.programId}/projects/${project.projectId}`,
      ),
    {
      onSuccess: async () => {
        await queryClient.invalidateQueries([
          'program',
          project.programId,
        ])
        notification.success({
          duration: 2,
          message: `Project ${project.name} has been deleted successfully`,
        })
        if (onDeleted) onDeleted()
      },
      onError: (err: Error) =>
        notification.error({
          duration: 2,
          message: `Project ${project.name} was not deleted`,
          description: err.message,
        }),
    },
  )
  const editProjectMutation = useMutation(
    (values: { name: string; description: string }) => {
      const data = { ...project, ...values }
      return axios.put(
        `/programs/${project.programId}/projects/${project.projectId}`,
        {
          project: { name: data.name, description: data.description },
        },
      )
    },
    {
      onSuccess: async () => {
        notification.success({
          duration: 2,
          message: `Project ${project.name} has been updated successfully`,
        })
        await queryClient.invalidateQueries([
          'program',
          project.programId,
        ])
        setIsEditProjectVisible(false)
      },
      onError: (err: Error) =>
        notification.error({
          duration: 2,
          message: `Project ${project.name} was not updated`,
          description: err.message,
        }),
    },
  )
  const [editProjectForm] = Form.useForm()

  return (
    <EditProjectDropdown>
      <Dropdown overlay={menu}>
        <MoreOutlined />
      </Dropdown>
      <Modal
        centered
        title="Edit Project Details"
        visible={isEditProjectVisible}
        okText="Save"
        onOk={() => {
          editProjectForm.validateFields().then((values) => {
            // editProjectForm.resetFields()
            editProjectMutation.mutate(values)
          })
        }}
        confirmLoading={editProjectMutation.isLoading}
        onCancel={() => setIsEditProjectVisible(false)}
      >
        <Form
          name="editProject"
          initialValues={{
            name: project.name,
            description: project.description,
          }}
          form={editProjectForm}
          layout="vertical"
        >
          <Form.Item
            label="Project Name"
            name="name"
            rules={[
              {
                required: true,
                message: 'Please input project name',
              },
            ]}
          >
            <Input />
          </Form.Item>
          <Form.Item
            label="Description"
            name="description"
            rules={[{ message: 'Please input project description' }]}
          >
            <Input.TextArea rows={4} />
          </Form.Item>
        </Form>
      </Modal>
    </EditProjectDropdown>
  )
}
Example #24
Source File: index.tsx    From RareCamp with Apache License 2.0 4 votes vote down vote up
export default function EditProgram({ program }) {
  const [isEditProgramVisible, setIsEditProgramVisible] =
    useState(false)
  const deleteProgram = () =>
    confirm({
      okButtonProps: {
        style: { backgroundColor: '#e53935', borderColor: '#e53935' },
      },
      title: 'Are you sure you want to delete this program?',
      centered: true,
      icon: <ExclamationCircleOutlined />,
      content:
        'Program will be immediately deleted. You cannot undo this action.',
      okText: 'Delete',
      onOk: deleteProgramMutation.mutateAsync,
      onCancel() {},
    })
  const menu = (
    <Menu>
      <Menu.Item>
        <Button
          onClick={() => setIsEditProgramVisible(true)}
          type="text"
        >
          Edit Program Details
        </Button>
      </Menu.Item>
      <Menu.Item>
        <Button onClick={deleteProgram} type="text">
          <Text type="danger">Delete Program</Text>
        </Button>
      </Menu.Item>
    </Menu>
  )
  const router = useRouter()
  const queryClient = useQueryClient()
  const deleteProgramMutation = useMutation(
    () =>
      axios.delete(
        `/workspaces/${program.workspaceId}/programs/${program.programId}`,
      ),
    {
      onSuccess: async () => {
        notification.success({
          duration: 2,
          message: `Program ${program.name} has been deleted successfully`,
        })
        const { data } = queryClient.getQueryData<any>(
          'defaultWorkspace',
        )
        queryClient.invalidateQueries('defaultWorkspace')
        if (data.workspace?.programs?.length) {
          await router.push('/workspace/intro')
        } else {
          await router.push('/')
        }
      },
      onError: (err: Error) =>
        notification.error({
          duration: 2,
          message: `Program ${program.name} was not deleted`,
          description: err.message,
        }),
    },
  )
  const editProgramMutation = useMutation(
    (values: { name: string; description: string }) => {
      const data = { ...program, ...values }
      return axios.put(
        `/workspaces/${program.workspaceId}/programs/${program.programId}`,
        {
          program: { name: data.name, description: data.description },
        },
      )
    },
    {
      onSuccess: async (resp) => {
        queryClient.setQueryData(['program', program.programId], {
          data: resp.data,
        })
        await queryClient.invalidateQueries('defaultWorkspace')
        notification.success({
          duration: 2,
          message: `Program ${program.name} has been updated successfully`,
        })
        setIsEditProgramVisible(false)
      },
      onError: (err: Error) =>
        notification.error({
          duration: 2,
          message: `Program ${program.name} was not updated`,
          description: err.message,
        }),
    },
  )
  const [editProgramForm] = Form.useForm()

  return (
    <EditProgramDropdown>
      <Dropdown overlay={menu}>
        <MoreOutlined />
      </Dropdown>
      <Modal
        centered
        title="Edit Program Details"
        visible={isEditProgramVisible}
        okText="Save"
        onOk={() => {
          editProgramForm.validateFields().then((values) => {
            // editProgramForm.resetFields()
            editProgramMutation.mutate(values)
          })
        }}
        confirmLoading={editProgramMutation.isLoading}
        onCancel={() => setIsEditProgramVisible(false)}
      >
        <Form
          name="editProgram"
          initialValues={{
            name: program.name,
            description: program.description,
          }}
          form={editProgramForm}
          layout="vertical"
        >
          <Form.Item
            label="Program Name"
            name="name"
            rules={[
              {
                required: true,
                message: 'Please input program name',
              },
            ]}
          >
            <Input />
          </Form.Item>
          <Form.Item
            label="Description"
            name="description"
            rules={[{ message: 'Please input program description' }]}
          >
            <Input.TextArea rows={4} />
          </Form.Item>
        </Form>
      </Modal>
    </EditProgramDropdown>
  )
}