@ant-design/icons#SyncOutlined TypeScript Examples

The following examples show how to use @ant-design/icons#SyncOutlined. 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: Fee.tsx    From subscan-multisig-react with Apache License 2.0 6 votes vote down vote up
export function Fee({ extrinsic }: FeeProps) {
  const { t } = useTranslation();
  const { fee, calcFee, setFee } = useFee();

  useEffect(() => {
    if (extrinsic) {
      calcFee(extrinsic);
    } else {
      setFee('Insufficient parameters');
    }
  }, [calcFee, extrinsic, setFee]);

  return <span className="flex items-center h-full">{fee === 'calculating' ? <SyncOutlined spin /> : t(fee)}</span>;
}
Example #2
Source File: index.tsx    From fe-v5 with Apache License 2.0 5 votes vote down vote up
function Refresh(props: IProps, ref) {
  const [intervalSeconds, setIntervalSeconds] = useState(intervalSecondsCache);
  const intervalRef = useRef<NodeJS.Timeout>();

  useEffect(() => {
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
    }
    if (intervalSeconds) {
      intervalRef.current = setInterval(() => {
        props.onRefresh();
      }, intervalSeconds * 1000);
    }
  }, [intervalSeconds]);

  useEffect(() => {
    return () => {
      if (intervalRef.current) {
        clearInterval(intervalRef.current);
      }
    };
  }, []);

  useImperativeHandle(ref, () => ({
    closeRefresh() {
      setIntervalSeconds(0);
      window.localStorage.setItem('refresh-interval-seconds', '0');
    },
  }));

  return (
    <div className='refresh-container'>
      <Button className='refresh-btn' icon={<SyncOutlined />} onClick={props.onRefresh} />
      <Dropdown
        trigger={['click']}
        overlay={
          <Menu
            onClick={(e) => {
              setIntervalSeconds(_.toNumber(e.key));
              window.localStorage.setItem('refresh-interval-seconds', _.toString(e.key));
            }}
          >
            {_.map(refreshMap, (text, value) => {
              return <Menu.Item key={value}>{text}</Menu.Item>;
            })}
          </Menu>
        }
      >
        <Button>
          {refreshMap[intervalSeconds]} <DownOutlined />
        </Button>
      </Dropdown>
    </div>
  );
}
Example #3
Source File: index.tsx    From nanolooker with MIT License 5 votes vote down vote up
RecentTransactions: React.FC = () => {
  const { t } = useTranslation();
  const { theme, disableLiveTransactions } = React.useContext(
    PreferencesContext,
  );

  const { recentTransactions, isConnected, isError } = useSockets();

  return (
    <Card
      size="small"
      title={t("pages.home.recentTransactions")}
      extra={<RecentTransactionsPreferences />}
    >
      <div
        className="sticky"
        style={{
          paddingBottom: "6px",
          zIndex: 1,
          background: theme === Theme.DARK ? "#1e1e1e" : "#fff",
        }}
      >
        <ConfirmationsPerSecond />
        {disableLiveTransactions ? (
          <div style={{ textAlign: "center" }}>
            {theme === Theme.DARK ? (
              <CloseCircleFilled style={{ color: TwoToneColors.SEND_DARK }} />
            ) : (
              <CloseCircleTwoTone twoToneColor={TwoToneColors.SEND} />
            )}
            <Text style={{ marginLeft: "8px" }} id="live-transactions-disabled">
              {t("pages.home.liveUpdatesDisabled")}
            </Text>
          </div>
        ) : null}
        {isConnected &&
        !disableLiveTransactions &&
        !recentTransactions.length ? (
          <div style={{ textAlign: "center" }}>
            <SyncOutlined spin />
            <Text style={{ marginLeft: "8px" }}>
              {t("pages.home.waitingForTransactions")} ...
            </Text>
          </div>
        ) : null}
        {!isConnected && !disableLiveTransactions ? (
          <div style={{ textAlign: "center" }}>
            <SyncOutlined spin />
            <Text style={{ marginLeft: "8px" }}>
              {isError
                ? t("pages.home.reconnectingToBlockchain")
                : t("pages.home.connectingToBlockchain")}
              ...
            </Text>
          </div>
        ) : null}
      </div>
      <div
        className="gradient-container"
        style={{
          maxHeight: "1260px",
          overflow: "hidden",
        }}
      >
        {recentTransactions.length ? (
          <>
            <Timeline recentTransactions={recentTransactions} />
            <div className="bottom-gradient" />
          </>
        ) : null}
      </div>
    </Card>
  );
}
Example #4
Source File: index.tsx    From fe-v5 with Apache License 2.0 5 votes vote down vote up
render() {
    const { spinning } = this.state;
    const { extraRender, data, showHeader = true } = this.props;
    const { title, metric } = data;
    const graphConfig = this.getGraphConfig(data);
    return (
      <div className={this.state.legend ? 'graph-container graph-container-hasLegend' : 'graph-container'}>
        {showHeader && (
          <div
            className='graph-header'
            style={{
              height: this.headerHeight,
              lineHeight: `${this.headerHeight}px`,
            }}
          >
            <div>{title || metric}</div>
            <div className='graph-extra'>
              <span className='graph-operationbar-item' key='info'>
                <Popover placement='left' content={this.getContent()} trigger='click' autoAdjustOverflow={false} getPopupContainer={() => document.body}>
                  <Button className='' type='link' size='small' onClick={(e) => e.preventDefault()}>
                    <SettingOutlined />
                  </Button>
                </Popover>
              </span>
              {this.props.isShowRefresh === false ? null : (
                <span className='graph-operationbar-item' key='sync'>
                  <Button type='link' size='small' onClick={(e) => e.preventDefault()}>
                    <SyncOutlined onClick={this.refresh} />
                  </Button>
                </span>
              )}
              {this.props.isShowShare === false ? null : (
                <span className='graph-operationbar-item' key='share'>
                  <Button type='link' size='small' onClick={(e) => e.preventDefault()}>
                    <ShareAltOutlined onClick={this.shareChart} />
                  </Button>
                </span>
              )}
              {extraRender && _.isFunction(extraRender) ? extraRender(this) : null}
            </div>
          </div>
        )}
        {this.props.graphConfigInnerVisible ? (
          <GraphConfigInner
            data={graphConfig}
            onChange={(...args) => {
              this.updateGraphConfig(args[2] || {});
            }}
          />
        ) : null}
        {/* 这个spin有点难搞,因为要第一时间获取chart容器的offsetheight */}
        {/* <Spin spinning={spinning} wrapperClassName='graph-spin'> */}
        {this.renderChart()}
        {/* </Spin> */}
        <Legend
          style={{ display: this.state.legend ? 'block' : 'none', overflowY: 'auto', maxHeight: '35%' }}
          graphConfig={graphConfig}
          series={this.getZoomedSeries()}
          onSelectedChange={this.handleLegendRowSelectedChange}
          comparisonOptions={graphConfig.comparisonOptions}
        />
      </div>
    );
  }
Example #5
Source File: index.tsx    From metaplex with Apache License 2.0 5 votes vote down vote up
function RunAction({
  id,
  action,
  onFinish,
  icon,
}: {
  id: string;
  action: () => Promise<boolean>;
  onFinish?: () => void;
  icon: JSX.Element;
}) {
  const [state, setRunState] = useState<RunActionState>(
    RunActionState.NotRunning,
  );

  useMemo(() => setRunState(RunActionState.NotRunning), [id]);

  const run = async () => {
    await setRunState(RunActionState.Running);
    const result = await action();
    if (result) {
      await setRunState(RunActionState.Success);
      setTimeout(() => (onFinish ? onFinish() : null), 2000); // Give user a sense of completion before removal from list
    } else {
      await setRunState(RunActionState.Failed);
    }
  };

  let component;
  switch (state) {
    case RunActionState.NotRunning:
      component = (
        <span className="hover-button" onClick={run}>
          {icon}
        </span>
      );
      break;
    case RunActionState.Failed:
      component = (
        <span className="hover-button" onClick={run}>
          <SyncOutlined />
        </span>
      );
      break;
    case RunActionState.Running:
      component = <LoadingOutlined />;
      break;
    case RunActionState.Success:
      component = <CheckCircleTwoTone twoToneColor="#52c41a" />;
  }

  return component;
}
Example #6
Source File: index.tsx    From jetlinks-ui-antd with MIT License 5 votes vote down vote up
GridLayout: React.FC<Props> = props => {
    const { layout, edit } = props;
    return (
        <>
            <ReactGridLayout
                onLayoutChange={(item: any) => {
                    // layoutChange(item)
                }}
                // cols={{ md: 12 }}
                // isResizable={edit}
                // isDraggable={edit}
                onDragStop={() => {
                    // setLayout([...layout])
                }}
                onResizeStop={() => {
                    // setLayout([...layout])
                }}
                className="layout"
                // layout={layout}
                rowHeight={30}
            >

                {layout.map((item: any) => (
                    <Card
                        style={{ overflow: "hidden" }}
                        key={item.i}
                        id={item.i}
                    >

                        <div style={{ position: 'absolute', right: 15, top: 5, }}>
                            <div style={{ float: 'right' }}>
                                <Fragment>
                                    {edit && (
                                        <>
                                            <Tooltip title="删除">
                                                <CloseCircleOutlined onClick={() => {
                                                    // removeCard(item)
                                                }} />
                                            </Tooltip>
                                            <Divider type="vertical" />
                                            <Tooltip title="编辑">
                                                <EditOutlined onClick={() => {
                                                    // setAddItem(true);
                                                    // setCurrent(item)
                                                }} />
                                            </Tooltip>
                                        </>)}
                                    {item.doReady &&
                                        <>
                                            <Divider type="vertical" />
                                            <Tooltip title="刷新">
                                                <SyncOutlined onClick={() => { item.doReady() }} />
                                            </Tooltip>

                                        </>}
                                </Fragment>
                            </div>
                        </div>
                        <GridCard
                            {...item}
                            productId={props.productId}
                            deviceId={props.target} />
                    </Card>))}


            </ReactGridLayout>
        </>
    )
}
Example #7
Source File: UpgradeSection.tsx    From posthog-foss with MIT License 5 votes vote down vote up
export function UpgradeSection(): JSX.Element {
    const { checkForUpdates, toggleSectionOpen } = useActions(pluginsLogic)
    const { sectionsOpen } = useValues(pluginsLogic)
    const { user } = useValues(userLogic)

    const {
        filteredPluginsNeedingUpdates,
        pluginsNeedingUpdates,
        checkingForUpdates,
        installedPluginUrls,
        updateStatus,
        rearranging,
        hasUpdatablePlugins,
    } = useValues(pluginsLogic)

    const upgradeButton = canInstallPlugins(user?.organization) && hasUpdatablePlugins && (
        <Button
            type="default"
            icon={pluginsNeedingUpdates.length > 0 ? <SyncOutlined /> : <CloudDownloadOutlined />}
            onClick={(e) => {
                e.stopPropagation()
                checkForUpdates(true)
            }}
            loading={checkingForUpdates}
        >
            {checkingForUpdates
                ? `Checking plugin ${Object.keys(updateStatus).length + 1} out of ${
                      Object.keys(installedPluginUrls).length
                  }`
                : pluginsNeedingUpdates.length > 0
                ? 'Check again for updates'
                : 'Check for updates'}
        </Button>
    )

    return (
        <>
            <div
                className="plugins-installed-tab-section-header"
                onClick={() => toggleSectionOpen(PluginSection.Upgrade)}
            >
                <Subtitle
                    subtitle={
                        <>
                            {sectionsOpen.includes(PluginSection.Upgrade) ? (
                                <CaretDownOutlined />
                            ) : (
                                <CaretRightOutlined />
                            )}
                            {` Plugins to update (${filteredPluginsNeedingUpdates.length})`}
                        </>
                    }
                    buttons={!rearranging && sectionsOpen.includes(PluginSection.Upgrade) && upgradeButton}
                />
            </div>
            {sectionsOpen.includes(PluginSection.Upgrade) ? (
                <>
                    {pluginsNeedingUpdates.length > 0 ? (
                        <Row gutter={16} style={{ marginTop: 16 }}>
                            {filteredPluginsNeedingUpdates.length > 0 ? (
                                <>
                                    {filteredPluginsNeedingUpdates.map((plugin) => (
                                        <InstalledPlugin key={plugin.id} plugin={plugin} showUpdateButton />
                                    ))}
                                </>
                            ) : (
                                <p style={{ margin: 10 }}>No plugins match your search.</p>
                            )}
                        </Row>
                    ) : (
                        <p style={{ margin: 10 }}>All your plugins are up to date. Great work!</p>
                    )}
                </>
            ) : null}
        </>
    )
}
Example #8
Source File: Metrics.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
export default function Metrics(props: IProps) {
  const { range, setRange, match } = props;
  const [refreshFlag, setRefreshFlag] = useState(_.uniqueId('refreshFlag_'));
  const [search, setSearch] = useState('');
  const [metrics, setMetrics] = useState([]);
  const [metricsDesc, setMetricsDesc] = useState({});
  const [activeKey, setActiveKey] = useState('all');
  const [metricPrefixes, setMetricPrefixes] = useState([]);
  const [selectedMetrics, setSelectedMetrics] = useState([]);
  const [step, setStep] = useState<number>();
  const matchStr = getMatchStr(match);
  const renderMetricList = (metrics = [], metricTabKey: string) => {
    const filtered = _.filter(metrics, (metric) => {
      let flag = true;
      flag = metricTabKey === 'all' ? true : metric.indexOf(metricTabKey) === 0;
      if (flag && search) {
        try {
          const reg = new RegExp(search, 'gi');
          flag = reg.test(metric);
        } catch (e) {
          flag = false;
        }
      }
      return flag;
    });
    return (
      <div className='tabPane' style={{ height: 240, overflow: 'auto' }}>
        {filtered.length ? (
          <ul className='n9e-metric-views-metrics-content' style={{ border: 'none' }}>
            {_.map(filtered, (metric, i) => {
              return (
                <li
                  className='item'
                  key={i}
                  onClick={() => {
                    setSelectedMetrics(_.union(_.concat(metric, selectedMetrics)));
                  }}
                >
                  <span>{metric}</span>
                  {_.find(selectedMetrics, (sm) => sm === metric) ? <span style={{ marginLeft: 8 }}>+1</span> : null}
                  {metricsDesc[metric] ? (
                    <Tooltip title={metricsDesc[metric]}>
                      <span className='desc'>{metricsDesc[metric]}</span>
                    </Tooltip>
                  ) : null}
                </li>
              );
            })}
          </ul>
        ) : (
          <div style={{ textAlign: 'center' }}>No data</div>
        )}
      </div>
    );
  };

  useEffect(() => {
    if (matchStr) {
      getMetricValues(matchStr, range).then((res) => {
        const _metrics = _.union(res);
        const metricPrefixes = _.union(
          _.compact(
            _.map(_metrics, (m) => {
              return _.get(_.split(m, '_'), '[0]');
            }),
          ),
        );
        setMetrics(_metrics);
        setMetricPrefixes(metricPrefixes);
        getMetricsDesc(_metrics).then((res) => {
          setMetricsDesc(res);
        });
      });
    }
  }, [refreshFlag, matchStr]);

  useEffect(() => {
    setSelectedMetrics([]);
    setActiveKey('all');
    setMetrics([]);
  }, [match.id, matchStr]);

  return (
    <div className='n9e-metric-views-metrics'>
      <div>
        <div className='n9e-metric-views-metrics-header'>
          <div className='metric-page-title'>监控指标</div>
          <Input
            prefix={<SearchOutlined />}
            value={search}
            onChange={(e) => {
              setSearch(e.target.value);
            }}
            placeholder='搜索,空格分隔多个关键字'
            addonAfter={
              <SyncOutlined
                style={{ cursor: 'pointer' }}
                onClick={() => {
                  setRefreshFlag(_.uniqueId('refreshFlag_'));
                }}
              />
            }
          />
        </div>
        <div>
          {metrics.length > 0 ? (
            <>
              <Card
                size='small'
                style={{ width: '100%' }}
                tabList={_.map(['all', ...metricPrefixes], (item) => {
                  return {
                    key: item,
                    tab: item,
                  };
                })}
                activeTabKey={activeKey}
                onTabChange={setActiveKey}
              >
                <div>{renderMetricList(metrics, activeKey)}</div>
              </Card>
              <Row style={{ padding: '10px 0' }}>
                <Col span={8}>
                  <Space>
                    <DateRangePicker
                      value={range}
                      onChange={(e) => {
                        setRange(e);
                      }}
                    />
                    <Resolution
                      onChange={(v) => {
                        setStep(v === null ? undefined : v);
                      }}
                      initialValue={step}
                    />
                    <Button
                      style={{ padding: '4px 8px' }}
                      onClick={() => {
                        setRange({
                          ...range,
                          refreshFlag: _.uniqueId('refreshFlag_'),
                        });
                      }}
                      icon={<SyncOutlined />}
                    ></Button>
                  </Space>
                </Col>
                <Col span={16} style={{ textAlign: 'right' }}>
                  <Button
                    onClick={() => {
                      setSelectedMetrics([]);
                    }}
                    disabled={!selectedMetrics.length}
                    style={{ background: '#fff' }}
                  >
                    清空图表
                  </Button>
                </Col>
              </Row>
              {_.map(selectedMetrics, (metric, i) => {
                return (
                  <Graph
                    key={metric}
                    metric={metric}
                    match={match}
                    range={range}
                    step={step}
                    onClose={() => {
                      const newselectedMetrics = [...selectedMetrics];
                      newselectedMetrics.splice(i, 1);
                      setSelectedMetrics(newselectedMetrics);
                    }}
                  />
                );
              })}
            </>
          ) : (
            <div style={{ marginTop: 12 }}>暂无指标数据,请选择左侧 Lables</div>
          )}
        </div>
      </div>
    </div>
  );
}
Example #9
Source File: palette.tsx    From jmix-frontend with Apache License 2.0 4 votes vote down vote up
palette = () =>
  <Palette>
    <Category name="Text">
      <Component name="Formatted Message">
        <Variant>
          <FormattedMessage />
        </Variant>
      </Component>
      <Component name="Heading">
        <Variant name='h1'>
          <Typography.Title></Typography.Title>
        </Variant>
        <Variant name='h2'>
          <Typography.Title level = {2}></Typography.Title>
        </Variant>
        <Variant name='h3'>
          <Typography.Title level = {3}></Typography.Title>
        </Variant>
        <Variant name='h4'>
          <Typography.Title level = {4}></Typography.Title>
        </Variant>
        <Variant name='h5'>
          <Typography.Title level = {5}></Typography.Title>
        </Variant>
      </Component>
      <Component name='Text'>
        <Variant>
          <Typography.Text></Typography.Text>
        </Variant>
        <Variant name = 'Secondary'>
          <Typography.Text type="secondary"></Typography.Text>
        </Variant>
        <Variant name = 'Success'>
          <Typography.Text type="success"></Typography.Text>
        </Variant>
        <Variant name = 'Warning'>
          <Typography.Text type="warning"></Typography.Text>
        </Variant>
        <Variant name = 'Danger'>
          <Typography.Text type="danger"></Typography.Text>
        </Variant>
        <Variant name = 'Disabled'>
          <Typography.Text disabled></Typography.Text>
        </Variant>
      </Component>
    </Category>
    <Category name="Layout">
      <Component name="Divider">
        <Variant>
          <Divider />
        </Variant>
      </Component>

      <Component name="Grid">
        <Variant name="Simple Row">
          <Row></Row>
        </Variant>
        <Variant name="Two columns">
          <Row>
            <Col span={12}></Col>
            <Col span={12}></Col>
          </Row>
        </Variant>
        <Variant name="Three columns">
          <Row>
            <Col span={8}></Col>
            <Col span={8}></Col>
            <Col span={8}></Col>
          </Row>
        </Variant>
      </Component>

      <Component name="Space">
        <Variant>
          <Space />
        </Variant>
        <Variant name="Small">
          <Space size={"small"} />
        </Variant>
        <Variant name="Large">
          <Space size={"large"} />
        </Variant>
      </Component>
    </Category>
    <Category name="Controls">
      <Component name="Autocomplete">
        <Variant>
          <AutoComplete placeholder="input here" />
        </Variant>
      </Component>

      <Component name="Button">
        <Variant>
          <Button></Button>
        </Variant>
        <Variant name="Primary">
          <Button type="primary" ></Button>
        </Variant>
        <Variant name="Link">
          <Button type="link" ></Button>
        </Variant>
        <Variant name="Dropdown">
          <Dropdown
            trigger={['click']}
            overlay={<Menu>
              <Menu.Item>
              </Menu.Item>
              <Menu.Item>
              </Menu.Item>
              <Menu.Item>
              </Menu.Item>
            </Menu>}
          >
            <Button></Button>
          </Dropdown>
        </Variant>
      </Component>

      <Component name="Checkbox">
        <Variant>
          <Checkbox />
        </Variant>
      </Component>

      <Component name='Switch'>
        <Variant>
          <Switch />
        </Variant>
      </Component>

      <Component name='Radio Group'>
        <Variant>
          <Radio.Group>
            <Radio value={1}>A</Radio>
            <Radio value={2}>B</Radio>
            <Radio value={3}>C</Radio>
            <Radio value={4}>D</Radio>
          </Radio.Group>
        </Variant>
        <Variant name = 'Button'>
          <Radio.Group>
            <Radio.Button value={1}>A</Radio.Button>
            <Radio.Button value={2}>B</Radio.Button>
            <Radio.Button value={3}>C</Radio.Button>
            <Radio.Button value={4}>D</Radio.Button>
          </Radio.Group>
        </Variant>
      </Component>

      <Component name="DatePicker">
        <Variant>
          <DatePicker />
        </Variant>
        <Variant name="Range">
          <DatePicker.RangePicker />
        </Variant>
      </Component>

      <Component name="TimePicker">
        <Variant>
          <TimePicker />
        </Variant>
        <Variant name="Range">
          <TimePicker.RangePicker />
        </Variant>
      </Component>

      <Component name="Input">
        <Variant>
          <Input />
        </Variant>
        <Variant name='Number'>
          <InputNumber />
        </Variant>
      </Component>

      <Component name='Select'>
        <Variant>
          <Select defaultValue="1">
            <Select.Option value="1">1</Select.Option>
            <Select.Option value="2">2</Select.Option>
          </Select>
        </Variant>
        <Variant name='Multiple'>
          <Select
            defaultValue={["1"]}
            mode="multiple"
            allowClear
          >
            <Select.Option value="1">1</Select.Option>
            <Select.Option value="2">2</Select.Option>
          </Select>
        </Variant>
      </Component>

      <Component name="Link">
        <Variant>
          <Typography.Link href="" target="_blank">
          </Typography.Link>
        </Variant>
      </Component>

      <Component name='Slider'>
        <Variant>
          <Slider defaultValue={30} />
        </Variant>
        <Variant name = 'Range'>
          <Slider range defaultValue={[20, 50]}/>
        </Variant>
      </Component>
    </Category>
    <Category name="Data Display">
    <Component name="Field">
        <Variant>
          <Field
            entityName={ENTITY_NAME}
            disabled={readOnlyMode}
            propertyName=''
            formItemProps={{
              style: { marginBottom: "12px" }
            }}
          />
        </Variant>
      </Component>
      <Component name="Card">
        <Variant>
          <Card />
        </Variant>
        <Variant name="With Title">
          <Card>
            <Card title="Card title">
              <p>Card content</p>
            </Card>
          </Card>
        </Variant>
        <Variant name="My custom card">
          <Card>
            <Card title="Card title">
              <p>Card content</p>
              <Avatar />
            </Card>
          </Card>
        </Variant>
      </Component>
      <Component name="Tabs">
        <Variant>
          <Tabs defaultActiveKey="1">
            <Tabs.TabPane tab="Tab 1" key="1">
              Content of Tab Pane 1
            </Tabs.TabPane>
            <Tabs.TabPane tab="Tab 2" key="2">
              Content of Tab Pane 2
            </Tabs.TabPane>
            <Tabs.TabPane tab="Tab 3" key="3">
              Content of Tab Pane 3
            </Tabs.TabPane>
          </Tabs>
        </Variant>
        <Variant name = "Tab Pane">
          <Tabs.TabPane>
          </Tabs.TabPane>
        </Variant>
      </Component>
      <Component name="Collapse">
        <Variant>
          <Collapse defaultActiveKey='1'>
            <Collapse.Panel header="This is panel header 1" key="1">
            </Collapse.Panel>
            <Collapse.Panel header="This is panel header 2" key="2">
            </Collapse.Panel>
            <Collapse.Panel header="This is panel header 3" key="3">
            </Collapse.Panel>
          </Collapse>
        </Variant>
      </Component>
      <Component name="Image">
        <Variant>
          <Image
            width={200}
            src=""
          />
        </Variant>
      </Component>
      <Component name="Avatar">
        <Variant>
          <Avatar icon={<UserOutlined />} />
        </Variant>
        <Variant name="Image">
          <Avatar src="https://joeschmoe.io/api/v1/random" />
        </Variant>
      </Component>
      <Component name="Badge">
        <Variant>
          <Badge count={1}>
          </Badge>
        </Variant>
      </Component>
      <Component name="Statistic">
        <Variant>
          <Statistic title="Title" value={112893} />
        </Variant>
      </Component>
      <Component name="Alert">
        <Variant name="Success">
          <Alert message="Text" type="success" />
        </Variant>
        <Variant name="Info">
          <Alert message="Text" type="info" />
        </Variant>
        <Variant name="Warning">
          <Alert message="Text" type="warning" />
        </Variant>
        <Variant name="Error">
          <Alert message="Text" type="error" />
        </Variant>
      </Component>
      <Component name='List'>
        <Variant>
          <List
            bordered
            dataSource={[]}
            renderItem={item => (
              <List.Item>
              </List.Item>
            )}
          />
        </Variant>
      </Component>
    </Category>
    <Category name="Icons">
      <Component name="Arrow">
        <Variant name = 'Up'>
          <ArrowUpOutlined />
        </Variant>
        <Variant name = 'Down'>
          <ArrowDownOutlined />
        </Variant>
        <Variant name = 'Left'>
          <ArrowLeftOutlined />
        </Variant>
        <Variant name = 'Right'>
          <ArrowRightOutlined />
        </Variant>
      </Component>
      <Component name = 'Question'>
        <Variant>
          <QuestionOutlined />
        </Variant>
        <Variant name = 'Circle'>
          <QuestionCircleOutlined />
        </Variant>
      </Component>
      <Component name = 'Plus'>
        <Variant>
          <PlusOutlined />
        </Variant>
        <Variant name = 'Circle'>
          <PlusCircleOutlined />
        </Variant>
      </Component>
      <Component name = 'Info'>
        <Variant>
          <InfoOutlined />
        </Variant>
        <Variant name = 'Circle'>
          <InfoCircleOutlined />
        </Variant>
      </Component>
      <Component name = 'Exclamation'>
        <Variant>
          <ExclamationOutlined />
        </Variant>
        <Variant name = 'Circle'>
          <ExclamationCircleOutlined />
        </Variant>
      </Component>
      <Component name = 'Close'>
        <Variant>
          <CloseOutlined />
        </Variant>
        <Variant name = 'Circle'>
          <CloseCircleOutlined />
        </Variant>
      </Component>
      <Component name = 'Check'>
        <Variant>
          <CheckOutlined />
        </Variant>
        <Variant name = 'Circle'>
          <CheckCircleOutlined />
        </Variant>
      </Component>
      <Component name = 'Edit'>
        <Variant>
          <EditOutlined />
        </Variant>
      </Component>
      <Component name = 'Copy'>
        <Variant>
          <CopyOutlined />
        </Variant>
      </Component>
      <Component name = 'Delete'>
        <Variant>
          <DeleteOutlined />
        </Variant>
      </Component>
      <Component name = 'Bars'>
        <Variant>
          <BarsOutlined />
        </Variant>
      </Component>
      <Component name = 'Bell'>
        <Variant>
          <BellOutlined />
        </Variant>
      </Component>
      <Component name = 'Clear'>
        <Variant>
          <ClearOutlined />
        </Variant>
      </Component>
      <Component name = 'Download'>
        <Variant>
          <DownloadOutlined />
        </Variant>
      </Component>
      <Component name = 'Upload'>
        <Variant>
          <UploadOutlined />
        </Variant>
      </Component>
      <Component name = 'Sync'>
        <Variant>
          <SyncOutlined />
        </Variant>
      </Component>
      <Component name = 'Save'>
        <Variant>
          <SaveOutlined />
        </Variant>
      </Component>
      <Component name = 'Search'>
        <Variant>
          <SearchOutlined />
        </Variant>
      </Component>
      <Component name = 'Settings'>
        <Variant>
          <SettingOutlined />
        </Variant>
      </Component>
      <Component name = 'Paperclip'>
        <Variant>
          <PaperClipOutlined />
        </Variant>
      </Component>
      <Component name = 'Phone'>
        <Variant>
          <PhoneOutlined />
        </Variant>
      </Component>
      <Component name = 'Mail'>
        <Variant>
          <MailOutlined />
        </Variant>
      </Component>
      <Component name = 'Home'>
        <Variant>
          <HomeOutlined />
        </Variant>
      </Component>
      <Component name = 'Contacts'>
        <Variant>
          <ContactsOutlined />
        </Variant>
      </Component>
      <Component name = 'User'>
        <Variant>
          <UserOutlined />
        </Variant>
        <Variant name = 'Add'>
          <UserAddOutlined />
        </Variant>
        <Variant name = 'Remove'>
          <UserDeleteOutlined />
        </Variant>
      </Component>
      <Component name = 'Team'>
        <Variant>
          <TeamOutlined />
        </Variant>
      </Component>
    </Category>
  </Palette>
Example #10
Source File: WidgetActionDropdown.tsx    From datart with Apache License 2.0 4 votes vote down vote up
WidgetActionDropdown: React.FC<WidgetActionDropdownProps> = memo(
  ({ widget }) => {
    const { editing: boardEditing } = useContext(BoardContext);

    const widgetAction = useWidgetAction();
    const dataChart = useContext(WidgetChartContext)!;
    const t = useI18NPrefix(`viz.widget.action`);
    const menuClick = useCallback(
      ({ key }) => {
        widgetAction(key, widget);
      },
      [widgetAction, widget],
    );
    const getAllList = useCallback(() => {
      const allWidgetActionList: WidgetActionListItem<widgetActionType>[] = [
        {
          key: 'refresh',
          label: t('refresh'),
          icon: <SyncOutlined />,
        },
        {
          key: 'fullScreen',
          label: t('fullScreen'),
          icon: <FullscreenOutlined />,
        },
        {
          key: 'edit',
          label: t('edit'),
          icon: <EditOutlined />,
        },
        {
          key: 'delete',
          label: t('delete'),
          icon: <DeleteOutlined />,
          danger: true,
        },

        {
          key: 'info',
          label: t('info'),
          icon: <InfoOutlined />,
        },
        {
          key: 'lock',
          label: t('lock'),
          icon: <LockOutlined />,
        },

        {
          key: 'makeLinkage',
          label: t('makeLinkage'),
          icon: <LinkOutlined />,
          divider: true,
        },
        {
          key: 'closeLinkage',
          label: t('closeLinkage'),
          icon: <CloseCircleOutlined />,
          danger: true,
        },
        {
          key: 'makeJump',
          label: t('makeJump'),
          icon: <BranchesOutlined />,
          divider: true,
        },
        {
          key: 'closeJump',
          label: t('closeJump'),
          icon: <CloseCircleOutlined />,
          danger: true,
        },
      ];
      return allWidgetActionList;
    }, [t]);
    const actionList = useMemo(() => {
      return (
        getWidgetActionList({
          allList: getAllList(),
          widget,
          boardEditing,
          chartGraphId: dataChart?.config?.chartGraphId,
        }) || []
      );
    }, [boardEditing, dataChart?.config?.chartGraphId, getAllList, widget]);
    const dropdownList = useMemo(() => {
      const menuItems = actionList.map(item => {
        return (
          <React.Fragment key={item.key}>
            {item.divider && <Menu.Divider />}
            <Menu.Item
              danger={item.danger}
              icon={item.icon}
              disabled={item.disabled}
              key={item.key}
            >
              {item.label}
            </Menu.Item>
          </React.Fragment>
        );
      });

      return <Menu onClick={menuClick}>{menuItems}</Menu>;
    }, [actionList, menuClick]);
    if (actionList.length === 0) {
      return null;
    }
    return (
      <Dropdown
        className="widget-tool-dropdown"
        overlay={dropdownList}
        placement="bottomCenter"
        trigger={['click']}
        arrow
      >
        <Button icon={<EllipsisOutlined />} type="link" />
      </Dropdown>
    );
  },
)
Example #11
Source File: index.tsx    From posthog-foss with MIT License 4 votes vote down vote up
export function ObjectTags({
    tags,
    onTagSave, // Required unless `staticOnly`
    onTagDelete, // Required unless `staticOnly`
    saving, // Required unless `staticOnly`
    tagsAvailable,
    style = {},
    staticOnly = false,
    id, // For pages that allow multiple object tags
}: ObjectTagsProps): JSX.Element {
    const [addingNewTag, setAddingNewTag] = useState(false)
    const [newTag, setNewTag] = useState('')
    const [deletedTags, setDeletedTags] = useState<string[]>([]) // we use this state var to remove items immediately from UI while API requests are processed

    const handleDelete = (tag: string, currentTags?: string[], propertyId?: string): void => {
        setDeletedTags([...deletedTags, tag])
        onTagDelete && onTagDelete(tag, currentTags, propertyId)
    }

    useEffect(() => {
        if (!saving) {
            setAddingNewTag(false)
            setNewTag('')
        }
    }, [saving])

    /** Displaying nothing is confusing, so in case of empty static tags we use a dash as a placeholder */
    const showPlaceholder = staticOnly && !tags.length
    if (showPlaceholder && !style.color) {
        style.color = 'var(--muted)'
    }

    return (
        <div style={style}>
            {showPlaceholder
                ? '—'
                : tags
                      .filter((t) => !!t)
                      .map((tag, index) => {
                          return (
                              <Tag
                                  key={index}
                                  color={COLOR_OVERRIDES[tag] || colorForString(tag)}
                                  style={{ marginTop: 8 }}
                              >
                                  {tag}{' '}
                                  {!staticOnly &&
                                      onTagDelete &&
                                      (deletedTags.includes(tag) ? (
                                          <SyncOutlined spin />
                                      ) : (
                                          <CloseOutlined
                                              style={{ cursor: 'pointer' }}
                                              onClick={() => handleDelete(tag, tags, id)}
                                          />
                                      ))}
                              </Tag>
                          )
                      })}
            {!staticOnly && onTagSave && saving !== undefined && (
                <span style={{ display: 'inline-flex', fontWeight: 400 }}>
                    <Tag
                        onClick={() => setAddingNewTag(true)}
                        data-attr="button-add-tag"
                        style={{
                            cursor: 'pointer',
                            borderStyle: 'dashed',
                            backgroundColor: '#ffffff',
                            display: addingNewTag ? 'none' : 'initial',
                        }}
                    >
                        <PlusOutlined /> New Tag
                    </Tag>
                    {addingNewTag && (
                        <SelectGradientOverflow
                            size="small"
                            onBlur={() => setAddingNewTag(false)}
                            data-attr="new-tag-input"
                            autoFocus
                            allowClear
                            autoClearSearchValue
                            defaultOpen
                            showSearch
                            style={{ width: 160 }}
                            onChange={(value) => {
                                onTagSave(value, tags, id)
                                setNewTag('')
                                setAddingNewTag(false)
                            }}
                            disabled={saving}
                            loading={saving}
                            onSearch={(newInput) => {
                                setNewTag(newInput)
                            }}
                            placeholder='try "official"'
                        >
                            {newTag ? (
                                <Select.Option
                                    key={`${newTag}_${id}`}
                                    value={newTag}
                                    className="ph-no-capture"
                                    data-attr="new-tag-option"
                                >
                                    New Tag: {newTag}
                                </Select.Option>
                            ) : (
                                (!tagsAvailable || !tagsAvailable.length) && (
                                    <Select.Option key="__" value="__" disabled style={{ color: 'var(--muted)' }}>
                                        Type to add a new tag
                                    </Select.Option>
                                )
                            )}
                            {tagsAvailable &&
                                tagsAvailable.map((tag) => (
                                    <Select.Option key={tag} value={tag} className="ph-no-capture">
                                        {tag}
                                    </Select.Option>
                                ))}
                        </SelectGradientOverflow>
                    )}
                </span>
            )}
        </div>
    )
}
Example #12
Source File: index.tsx    From nanolooker with MIT License 4 votes vote down vote up
BlockDetails: React.FC = () => {
  const { t } = useTranslation();
  const { theme, fiat } = React.useContext(PreferencesContext);
  const {
    marketStatistics: {
      currentPrice,
      priceStats: { bitcoin: { [fiat]: btcCurrentPrice = 0 } } = {
        bitcoin: { [fiat]: 0 },
      },
    },
    isInitialLoading: isMarketStatisticsInitialLoading,
  } = React.useContext(MarketStatisticsContext);
  const {
    blocks,
    blocksInfo,
    isLoading: isBlocksInfoLoading,
  } = React.useContext(BlocksInfoContext);
  const { knownAccounts } = React.useContext(KnownAccountsContext);
  const isSmallAndLower = !useMediaQuery("(min-width: 576px)");

  const skeletonProps = {
    active: true,
    paragraph: false,
    loading: isBlocksInfoLoading,
  };

  const blockInfo = blocksInfo?.blocks?.[blocks[0]];

  const {
    subtype,
    block_account: blockAccount,
    source_account: sourceAccount,
    height,
    contents: {
      type = "",
      representative = "",
      link = "",
      link_as_account: linkAsAccount = "",
      previous = "",
      signature = "",
      work = "",
    } = {},
    successor,
  } = blockInfo || {};

  const modifiedTimestamp = Number(blockInfo?.local_timestamp) * 1000;

  const amount = new BigNumber(rawToRai(blockInfo?.amount || 0)).toNumber();
  const fiatAmount = new BigNumber(amount)
    .times(currentPrice)
    .toFormat(CurrencyDecimal?.[fiat]);
  const btcAmount = new BigNumber(amount)
    .times(currentPrice)
    .dividedBy(btcCurrentPrice)
    .toFormat(12);

  const balance = new BigNumber(rawToRai(blockInfo?.balance || 0)).toNumber();
  const fiatBalance = new BigNumber(balance)
    .times(currentPrice)
    .toFormat(CurrencyDecimal?.[fiat]);
  const btcBalance = new BigNumber(balance)
    .times(currentPrice)
    .dividedBy(btcCurrentPrice)
    .toFormat(12);

  let linkAccountLabel = "";
  if (subtype === "send") {
    linkAccountLabel = t("pages.block.receiver");
  } else if (subtype === "receive") {
    linkAccountLabel = t("pages.block.sender");
  }

  const secondAccount = isValidAccountAddress(sourceAccount || "")
    ? sourceAccount
    : linkAsAccount;

  const blockAccountAlias = knownAccounts.find(
    ({ account: knownAccount }) => knownAccount === blockAccount,
  )?.alias;
  const secondAccountAlias = knownAccounts.find(
    ({ account: knownAccount }) => knownAccount === secondAccount,
  )?.alias;
  const representativeAlias = knownAccounts.find(
    ({ account: knownAccount }) => knownAccount === representative,
  )?.alias;

  const isConfirmed = toBoolean(blockInfo?.confirmed);

  return (
    <>
      {!isBlocksInfoLoading && !blockInfo ? (
        <Card bordered={false}>
          <Title level={3}>{t("pages.block.blockNotFound")}</Title>
          <Text>{t("pages.block.blockNotFoundInfo")}</Text>
        </Card>
      ) : null}
      {isBlocksInfoLoading || blockInfo ? (
        <>
          <Card
            size="small"
            bordered={false}
            className="detail-layout"
            style={{ marginBottom: "12px" }}
          >
            <Row gutter={6}>
              <Col xs={24}>
                <BlockHeader />
              </Col>
            </Row>
            <Row gutter={6}>
              {isSmallAndLower ? null : (
                <Col xs={24} sm={6} xl={4}>
                  {t("pages.block.blockSubtype")}
                </Col>
              )}
              <Col xs={24} sm={18} xl={20}>
                <Skeleton
                  {...skeletonProps}
                  title={{ width: isSmallAndLower ? "50%" : "20%" }}
                >
                  <Tooltip
                    placement={isSmallAndLower ? "right" : "top"}
                    title={t(
                      `pages.block.${
                        isConfirmed ? "confirmed" : "pending"
                      }Status`,
                    )}
                  >
                    <Tag
                      icon={
                        isConfirmed ? (
                          <CheckCircleOutlined />
                        ) : (
                          <SyncOutlined spin />
                        )
                      }
                      color={
                        // @ts-ignore
                        TwoToneColors[
                          `${(subtype || type).toUpperCase()}${
                            theme === Theme.DARK ? "_DARK" : ""
                          }`
                        ]
                      }
                      className={`tag-${subtype || type}`}
                    >
                      {t(`transaction.${subtype || type}`)}
                    </Tag>
                  </Tooltip>
                </Skeleton>
              </Col>
            </Row>
            <Row gutter={6}>
              <Col xs={24} sm={6} xl={4}>
                {t("common.account")}
              </Col>
              <Col xs={24} sm={18} xl={20}>
                <Skeleton {...skeletonProps}>
                  {blockAccountAlias ? (
                    <strong style={{ display: "block" }}>
                      {blockAccountAlias}
                    </strong>
                  ) : null}
                  <Link to={`/account/${blockAccount}`} className="break-word">
                    {blockAccount}
                  </Link>
                </Skeleton>
              </Col>
            </Row>
            <Row gutter={6}>
              <Col xs={24} sm={6} xl={4}>
                {t("transaction.amount")}
              </Col>
              <Col xs={24} sm={18} xl={20}>
                <LoadingStatistic
                  isLoading={skeletonProps.loading}
                  prefix="Ӿ"
                  value={
                    amount >= 1 ? amount : new BigNumber(amount).toFormat()
                  }
                />
                <Skeleton
                  {...skeletonProps}
                  loading={
                    skeletonProps.loading || isMarketStatisticsInitialLoading
                  }
                  title={{ width: isSmallAndLower ? "100%" : "33%" }}
                >
                  {`${CurrencySymbol?.[fiat]} ${fiatAmount} / ${btcAmount} BTC`}
                </Skeleton>
              </Col>
            </Row>
            <Row gutter={6}>
              <Col xs={24} sm={6} xl={4}>
                {t("common.balance")}
              </Col>
              <Col xs={24} sm={18} xl={20}>
                <Skeleton
                  {...skeletonProps}
                  title={{ width: isSmallAndLower ? "100%" : "33%" }}
                >
                  Ӿ {new BigNumber(balance).toFormat()}
                  <br />
                </Skeleton>
                <Skeleton
                  {...skeletonProps}
                  loading={
                    skeletonProps.loading || isMarketStatisticsInitialLoading
                  }
                  title={{ width: isSmallAndLower ? "100%" : "33%" }}
                >
                  {`${CurrencySymbol?.[fiat]} ${fiatBalance} / ${btcBalance} BTC`}
                </Skeleton>
              </Col>
            </Row>
            {linkAccountLabel ? (
              <Row gutter={6}>
                <Col xs={24} sm={6} xl={4}>
                  {linkAccountLabel}
                </Col>
                <Col xs={24} sm={18} xl={20}>
                  {secondAccountAlias ? (
                    <strong
                      style={{
                        display: "block",
                      }}
                    >
                      {secondAccountAlias}
                    </strong>
                  ) : null}
                  <Link to={`/account/${secondAccount}`} className="break-word">
                    {secondAccount}
                  </Link>
                </Col>
              </Row>
            ) : null}
            {representative ? (
              <Row gutter={6}>
                <Col xs={24} sm={6} xl={4}>
                  {t("common.representative")}
                </Col>
                <Col xs={24} sm={18} xl={20}>
                  {representativeAlias ? (
                    <strong
                      style={{
                        display: "block",
                      }}
                    >
                      {representativeAlias}
                    </strong>
                  ) : null}
                  <Link
                    to={`/account/${representative}`}
                    className="break-word"
                  >
                    {representative}
                  </Link>
                </Col>
              </Row>
            ) : null}
            <Row gutter={6}>
              <Col xs={24} sm={6} xl={4}>
                {t("pages.block.height")}
              </Col>
              <Col xs={24} sm={18} xl={20}>
                <Skeleton {...skeletonProps}>{height}</Skeleton>
              </Col>
            </Row>
            {modifiedTimestamp ? (
              <Row gutter={6}>
                <Col xs={24} sm={6} xl={4}>
                  {t("common.date")}
                </Col>
                <Col xs={24} sm={18} xl={20}>
                  {timestampToDate(modifiedTimestamp)}{" "}
                  <span className="color-muted" style={{ fontSize: "12px" }}>
                    (
                    <TimeAgo
                      locale={i18next.language}
                      datetime={modifiedTimestamp}
                      live={false}
                    />
                    )
                  </span>
                </Col>
              </Row>
            ) : null}
            <Row gutter={6}>
              <Col xs={24} sm={6} xl={4}>
                {t("pages.block.previousBlock")}
              </Col>
              <Col xs={24} sm={18} xl={20}>
                <Skeleton
                  {...skeletonProps}
                  title={{ width: isSmallAndLower ? "100%" : "50%" }}
                >
                  {isValidBlockHash(previous) ? (
                    <Link to={`/block/${previous}`} className="break-word">
                      {previous}
                    </Link>
                  ) : null}
                  {isNullAccountBlockHash(previous) ? (
                    <Text>{t("pages.block.openAccountBlock")}</Text>
                  ) : null}
                </Skeleton>
              </Col>
            </Row>
            <Row gutter={6}>
              <Col xs={24} sm={6} xl={4}>
                {t("pages.block.successorBlock")}
              </Col>
              <Skeleton
                {...skeletonProps}
                title={{ width: isSmallAndLower ? "100%" : "50%" }}
              ></Skeleton>
              <Col xs={24} sm={18} xl={20}>
                {isValidBlockHash(successor) ? (
                  <Link to={`/block/${successor}`} className="break-word">
                    {successor}
                  </Link>
                ) : null}
                {isNullAccountBlockHash(successor) ? (
                  <Text>{t("pages.block.lastAccountBlock")}</Text>
                ) : null}
              </Col>
            </Row>
            {link && subtype === "receive" ? (
              <Row gutter={6}>
                <Col xs={24} sm={6} xl={4}>
                  {t("pages.block.matchingSendBlock")}
                </Col>
                <Skeleton
                  {...skeletonProps}
                  title={{ width: isSmallAndLower ? "100%" : "50%" }}
                ></Skeleton>
                <Col xs={24} sm={18} xl={20}>
                  {isValidBlockHash(link) ? (
                    <Link to={`/block/${link}`} className="break-word">
                      {link}
                    </Link>
                  ) : (
                    t("pages.block.noMatchingSendBlock")
                  )}
                </Col>
              </Row>
            ) : null}
            <Row gutter={6}>
              <Col xs={24} sm={6} xl={4}>
                {t("pages.block.signature")}
              </Col>
              <Col xs={24} sm={18} xl={20}>
                <Skeleton {...skeletonProps}>
                  <span className="break-word">{signature}</span>
                </Skeleton>
              </Col>
            </Row>
            <Row gutter={6}>
              <Col xs={24} sm={6} xl={4}>
                {t("pages.block.work")}
              </Col>
              <Col xs={24} sm={18} xl={20}>
                <Skeleton
                  {...skeletonProps}
                  title={{ width: isSmallAndLower ? "100%" : "33%" }}
                >
                  {work}
                </Skeleton>
              </Col>
            </Row>
          </Card>

          <Title level={3}>{t("pages.block.originalBlockContent")}</Title>
          <Card size="small">
            <Skeleton {...skeletonProps} paragraph>
              <pre style={{ fontSize: "12px", marginBottom: 0 }}>
                {JSON.stringify(blockInfo, null, 2)}
              </pre>
            </Skeleton>
          </Card>
        </>
      ) : null}
    </>
  );
}
Example #13
Source File: index.tsx    From nanolooker with MIT License 4 votes vote down vote up
TransactionsTable = ({
  scrollTo,
  data,
  isLoading,
  showPaginate,
  isPaginated,
  pageSize,
  currentPage,
  totalPages,
  setCurrentPage,
  setCurrentHead,
}: TransactionsTableProps) => {
  const { t } = useTranslation();
  const { theme, natricons } = React.useContext(PreferencesContext);
  const { knownAccounts } = React.useContext(KnownAccountsContext);

  const isLargeAndHigher = useMediaQuery("(min-width: 992px)");
  const smallNatriconSize = !useMediaQuery("(min-width: 768px)");

  return (
    <Card size="small" className="transaction-card" id={scrollTo}>
      {isLoading ? (
        <div className="ant-spin-nested-loading">
          <div>
            <div className="ant-spin ant-spin-spinning">
              <span className="ant-spin-dot ant-spin-dot-spin">
                <i className="ant-spin-dot-item"></i>
                <i className="ant-spin-dot-item"></i>
                <i className="ant-spin-dot-item"></i>
                <i className="ant-spin-dot-item"></i>
              </span>
            </div>
          </div>
        </div>
      ) : null}
      {isLargeAndHigher ? (
        <Row
          gutter={[{ xs: 6, sm: 12, md: 12, lg: 12 }, 12]}
          className="row-header color-muted"
        >
          <Col xs={0} lg={2}>
            {t("transaction.type")}
          </Col>
          {natricons ? <Col xs={0} lg={2}></Col> : null}
          <Col xs={0} lg={natricons ? 12 : 14}>
            {t("transaction.accountAndBlock")}
          </Col>
          <Col xs={0} lg={5}>
            {t("transaction.amount")}
          </Col>
          <Col xs={0} lg={3} style={{ textAlign: "right" }}>
            {t("common.date")}
          </Col>
        </Row>
      ) : null}
      {data?.length ? (
        <>
          {data.map(
            (
              {
                subtype,
                type,
                account: historyAccount,
                amount,
                representative,
                hash,
                confirmed,
                local_timestamp: localTimestamp,
              }: History,
              index: number,
            ) => {
              const transactionType = subtype || type;
              const themeColor = `${transactionType.toUpperCase()}${
                theme === Theme.DARK ? "_DARK" : ""
              }`;
              // When transaction is a representative change, the account is the representative
              const account =
                transactionType === "change" ? representative : historyAccount;
              const knownAccount =
                account &&
                knownAccounts.find(
                  ({ account: knownAccount }) => account === knownAccount,
                );

              const modifiedTimestamp = Number(localTimestamp) * 1000;
              const modifiedDate = new Date(modifiedTimestamp);

              return (
                <Row
                  key={index}
                  justify="space-between"
                  align="middle"
                  gutter={[12, 12]}
                >
                  <Col
                    xs={natricons ? 12 : 24}
                    md={4}
                    lg={2}
                    className="gutter-row"
                    span={6}
                  >
                    <Tooltip
                      placement="right"
                      title={
                        typeof confirmed !== "undefined"
                          ? t(
                              `pages.block.${
                                toBoolean(confirmed) === false
                                  ? "pending"
                                  : "confirmed"
                              }Status`,
                            )
                          : null
                      }
                    >
                      <Tag
                        // @ts-ignore
                        color={TwoToneColors[themeColor]}
                        style={{ textTransform: "capitalize" }}
                        className={`tag-${subtype || type}`}
                        icon={
                          typeof confirmed !== "undefined" ? (
                            toBoolean(confirmed) === false ? (
                              <SyncOutlined spin />
                            ) : (
                              <CheckCircleOutlined />
                            )
                          ) : null
                        }
                      >
                        {t(`transaction.${transactionType}`)}
                      </Tag>
                    </Tooltip>
                  </Col>
                  {natricons ? (
                    <Col xs={12} md={2} style={{ textAlign: "right" }}>
                      <Natricon
                        account={account}
                        style={{
                          margin: "-12px -6px -18px -18px ",
                          width: `${smallNatriconSize ? 60 : 80}px`,
                          height: `${smallNatriconSize ? 60 : 80}px`,
                        }}
                      />
                    </Col>
                  ) : null}
                  <Col
                    xs={24}
                    md={natricons ? 18 : 20}
                    lg={natricons ? 12 : 14}
                  >
                    {knownAccount ? (
                      <div className="color-important">
                        {knownAccount.alias}
                      </div>
                    ) : null}
                    {account ? (
                      <Link
                        to={`/account/${account}`}
                        className="break-word color-normal"
                      >
                        {account}
                      </Link>
                    ) : (
                      t("common.notAvailable")
                    )}

                    <br />
                    <Link
                      to={`/block/${hash}`}
                      className="color-muted truncate"
                    >
                      {hash}
                    </Link>
                  </Col>
                  <Col xs={16} md={12} lg={5}>
                    <Text
                      // @ts-ignore
                      style={{ color: Colors[themeColor] }}
                      className="break-word"
                    >
                      {!amount || amount === "0"
                        ? t("common.notAvailable")
                        : ""}
                      {amount && amount !== "0"
                        ? `Ӿ ${new BigNumber(rawToRai(amount)).toFormat()}`
                        : ""}
                    </Text>
                  </Col>
                  <Col xs={8} md={12} lg={3} style={{ textAlign: "right" }}>
                    {Number(localTimestamp) ? (
                      <>
                        {modifiedDate.getFullYear()}/
                        {String(modifiedDate.getMonth() + 1).padStart(2, "0")}/
                        {String(modifiedDate.getDate()).padStart(2, "0")}
                        <br />
                        <TimeAgo
                          locale={i18next.language}
                          style={{ fontSize: "12px" }}
                          className="color-muted"
                          datetime={modifiedTimestamp}
                          live={false}
                        />
                      </>
                    ) : (
                      t("common.unknown")
                    )}
                  </Col>
                </Row>
              );
            },
          )}
          {showPaginate ? (
            <Row className="row-pagination">
              {isPaginated ? (
                <Col xs={24} style={{ textAlign: "right" }}>
                  <Pagination
                    size="small"
                    {...{
                      total: totalPages,
                      pageSize,
                      current: currentPage,
                      disabled: false,
                      onChange: (page: number) => {
                        if (scrollTo) {
                          const element = document.getElementById(scrollTo);
                          element?.scrollIntoView();
                        }

                        setCurrentPage?.(page);
                      },
                      showSizeChanger: false,
                    }}
                  />
                </Col>
              ) : null}
              {!isPaginated && setCurrentHead ? (
                <Col xs={24} style={{ textAlign: "center" }}>
                  <Button
                    // @ts-ignore
                    onClick={setCurrentHead}
                    type={theme === Theme.DARK ? "primary" : "default"}
                  >
                    {t("pages.account.loadMoreTransactions")}
                  </Button>
                </Col>
              ) : null}
            </Row>
          ) : null}
        </>
      ) : (
        <Empty
          image={Empty.PRESENTED_IMAGE_SIMPLE}
          style={{ padding: "12px" }}
        />
      )}
    </Card>
  );
}
Example #14
Source File: index.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
StaffList: React.FC = () => {
  const [syncLoading, setSyncLoading] = useState<boolean>(false);
  const [currentDepartment, setCurrentDepartment] = useState('0');
  const actionRef = useRef<ActionType>();

  const getDepartmentKey = (key: string) => {
    setCurrentDepartment(key)
  }

  const columns: ProColumns<StaffList.Item>[] = [
    {
      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',
      order: 100,
      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: 180,
      render: (text, dom) => {
        return (
          <Space>
            {
              dom.enable_msg_arch === 2 ? <Tooltip placement="topLeft" title="该员工暂未开启消息存档">
                  <Button type={'link'} disabled={true}>聊天记录</Button>
                </Tooltip>
                :
                <Button type={'link'}
                        onClick={() => history.push(`/staff-admin/corp-risk-control/chat-session?staff=${dom.ext_staff_id}`)}
                >聊天记录</Button>
            }
            <Button type={'link'}
                    onClick={() => history.push(`/staff-admin/company-management/role?ext_staff_id=${dom.ext_staff_id}`)}>管理权限</Button>
          </Space>
        );
      },
    },
  ];
  return (
    <PageContainer
      fixedHeader
      header={{
        title: '员工管理',
      }}
      extra={[
        <Button
          key={'sync'}
          type="dashed"
          icon={<SyncOutlined style={{fontSize: 16, verticalAlign: '-3px'}}/>}
          loading={syncLoading}
          onClick={async () => {
            setSyncLoading(true);
            const res: CommonResp = await Sync();
            if (res.code === 0) {
              setSyncLoading(false);
              // @ts-ignore
              actionRef?.current.reset();
              message.success('同步成功');
            } else {
              setSyncLoading(false);
              message.error(res.message);
            }
          }}
        >
          同步数据
        </Button>,
      ]}
    >
      {!localStorage.getItem('customerLossMemberManagementTipsClosed') && (
        <Alert
          showIcon={true}
          closable={true}
          style={{marginBottom: 16}}
          type="info"
          message={
            <Text type={'secondary'}>
              部门数据与企业微信同步,若需要修改员工部门请前往企业微信设置
              <Button type={'link'} onClick={()=>window.open('https://work.weixin.qq.com/wework_admin/loginpage_wx?from=myhome')}>去设置</Button>
            </Text>
          }
          onClick={() => {
            localStorage.setItem('customerLossMemberManagementTipsClosed', '1');
          }}
        />
      )}
      <ProTable
        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}
        tableRender={(_, dom) => (
          <div className={styles.mixedTable}>
            <div className={styles.leftPart}>
              <DepartmentTree callback={getDepartmentKey}/>
            </div>
            <div className={styles.rightPart}>
              <div className={styles.tableWrap}>{dom}</div>
            </div>
          </div>
        )}
        params={{
          ext_department_ids: currentDepartment !== '0' ? currentDepartment : '0',
        }}
        request={async (params, sort, filter) => {
          return ProTableRequestAdapter(params, sort, filter, QueryStaffsList);
        }}
        dateFormatter="string"
      />
    </PageContainer>
  );
}
Example #15
Source File: ScriptModal.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
ScriptModal: (props: ScriptModalProps, ref: any) => JSX.Element = (props, ref) => {
  const formRef = useRef<FormInstance>();
  const [fileInfoAry, setFileInfoAry] = useState<{ fileName: string, fileSize: string, key: number }[]>([]) // PDF的文件信息数组
  const groupModalRef = useRef<any>({});
  const [groupModalVisible, setGroupModalVisible] = useState(false);
  const [groupItems, setGroupItems] = useState<Partial<ScriptGroup.Item>[]>([]);
  const [groupItemsTimestamp, setGroupItemsTimestamp] = useState(Date.now);
  const [deletedIds,setDeletedIds] = useState<number[]>([])

  React.useImperativeHandle(ref, () => {
    return {
      open: (item: Partial<Script.Item>) => {
        setGroupItemsTimestamp(Date.now)
        setTimeout(() => {
          formRef?.current?.setFieldsValue(item)
        }, 100)
        props.setVisible(true)
      },
      close: () => {
        formRef?.current?.resetFields()
        props.setVisible(false)
      }
    }
  })

  useEffect(() => {
    QueryEnterpriseScriptGroups({page_size: 5000}).then(res => {
      if (res?.code === 0 && res?.data) {
        setGroupItems(res?.data?.items || [])
      }
    }).catch((err) => {
      message.error(err);
    });
  }, [groupItemsTimestamp])

  // 转为传给后端的数据
  const transferParams = (params: any) => {
    const newReplyDetails = []
    for (let i = 0; i < params.reply_details.length; i += 1) {
      const typeObjectKey = typeEnums[params.reply_details[i].content_type]
      const replyDetailItem = {content_type: params.reply_details[i].content_type, id: params.reply_details[i].id || '',quick_reply_content: {}}
      const item = params.reply_details[i]
      if (typeObjectKey === 'text') {
        const quickReplyContent = {
          text: {
            'content': item.text_content
          }
        }
        replyDetailItem.quick_reply_content = quickReplyContent
        newReplyDetails.push(replyDetailItem)
      }
      if (typeObjectKey === 'image') {
        const quickReplyContent = {
          image: {
            'title': item.image_title,
            'size': item.image_size,
            'picurl': item.image_picurl
          }
        }
        replyDetailItem.quick_reply_content = quickReplyContent
        newReplyDetails.push(replyDetailItem)
      }
      if (typeObjectKey === 'link') {
        const quickReplyContent = {
          link: {
            'title': item.link_title,
            'picurl': item.link_picurl,
            'desc': item.link_desc,
            'url': item.link_url
          }
        }
        replyDetailItem.quick_reply_content = quickReplyContent
        newReplyDetails.push(replyDetailItem)
      }
      if (typeObjectKey === 'pdf') {
        const quickReplyContent = {
          pdf: {
            'title': item.pdf_title,
            'size': item.pdf_size,
            'fileurl': item.pdf_fileurl
          }
        }
        replyDetailItem.quick_reply_content = quickReplyContent
        newReplyDetails.push(replyDetailItem)
      }
      if (typeObjectKey === 'video') {
        const quickReplyContent = {
          video: {
            'title': item.video_title,
            'size': item.video_size,
            'picurl': item.video_picurl
          }
        }
        replyDetailItem.quick_reply_content = quickReplyContent
        newReplyDetails.push(replyDetailItem)
      }
    }
    return {...params, reply_details: newReplyDetails, id: props.initialValues?.id,deleted_ids:deletedIds||[]}
  }

  return (
    <div className={styles.scriptModalContainer}>
      <Modal
        {...props}
        width={640}
        className={'dialog from-item-label-100w'}
        visible={props.visible}
        onOk={() => {
          setFileInfoAry([])
          formRef?.current?.submit();
        }}
        onCancel={() => {
          props.setVisible(false)
          formRef?.current?.resetFields()
          props.onCancel()
          setFileInfoAry([])
        }}
      >
        <ProForm
          formRef={formRef}
          layout={'horizontal'}
          submitter={{
            resetButtonProps: {
              style: {
                display: 'none',
              },
            },
            submitButtonProps: {
              style: {
                display: 'none',
              },
            },
          }}
          onFinish={async (values) => {
            const params: any = transferParams(values)
            if (values.is_global === True) {
              params.departments = [0];
            }
            await props.onFinish(params, props.initialValues?.id ? 'update' : 'create');
          }}

        >
          <h3 className="dialog-title" style={{fontSize: 18}}>
            {props.initialValues?.id ? '修改话术' : '新建话术'}
          </h3>

          <ProForm.Item
            name={'group_id'}
            label="分组"
            rules={[{required: true, message: '请选择分组!'}]}
          >
            <Select
              style={{width: 400}}
              placeholder="请选择分组"
              dropdownRender={menu => (
                <div>
                  {menu}
                  <Divider style={{margin: '4px 0'}}/>
                  <div style={{display: 'flex', flexWrap: 'nowrap', padding: 8}}>
                    <a
                      style={{flex: 'none', display: 'block', cursor: 'pointer'}}
                      onClick={() => {
                        groupModalRef.current.open({sub_group: [{name: ''}]})
                      }}
                    >
                      <PlusOutlined/> 新建分组
                    </a>
                  </div>
                </div>
              )}
            >
              {
                groupItems?.map(item => (
                  <Option key={item.id} value={String(item.id)}>
                    <FolderFilled style={{fontSize: '16px', color: '#138af8', marginRight: 8}}/>
                    {item.name}
                  </Option>
                ))
              }
            </Select>
          </ProForm.Item>

          <ProForm.Item name="name" label="标题" rules={[{required: true, message: '请输入标题!'}]} >
            <Input placeholder="仅内部可见,方便整理和查看" style={{width: 400}} maxLength={10}/>
          </ProForm.Item>

          <ProFormList
            name="reply_details"
            creatorButtonProps={{
              type: 'default',
              style: {width: '128px', color: '#58adfc', borderColor: '#58adfc', marginLeft: '20px', marginTop: '-30px'},
              position: 'bottom',
              creatorButtonText: '添加内容',
            }}
            creatorRecord={{
              content_type: 2,
            }}
            // eslint-disable-next-line @typescript-eslint/no-unused-vars
            itemRender={({listDom}, {field, record, operation}) => {
              const currentKey = field.name;
              // const boundKey = field.fieldKey;
              return (
                <div className={styles.dynamicFormContainer}>
                  <div>
                    <div className={styles.radioBox}>
                      <ProFormRadio.Group
                        name="content_type"
                        initialValue={'word'}
                        label=""
                        options={[
                          {
                            label: '文字',
                            value: 2,
                          },
                          {
                            label: '图片',
                            value: 3,
                          },
                          {
                            label: '网页',
                            value: 4,
                          },
                          {
                            label: 'PDF',
                            value: 5,
                          },
                          {
                            label: '视频',
                            value: 6,
                          },
                        ]}
                      />
                    </div>
                    <ProForm.Item name="id" label="" style={{display: 'none'}}>
                      <Input />
                    </ProForm.Item>
                    <div className={styles.tabContent}>
                      <ProFormDependency name={['content_type']}>
                        {({content_type}) => {
                          if (content_type === 2) {
                            return (
                              <ProFormTextArea
                                name="text_content"
                                label={'话术内容'}
                                placeholder="请输入话术内容"
                                rules={[{required: true, message: '请输入话术内容!'}]}
                                fieldProps={{showCount: true, maxLength: 1300, allowClear: true}}
                              />
                            );
                          }
                          if (content_type === 3) {
                            return <div>
                              <ProForm.Item name="image_title" label="图片名称"
                                            rules={[{required: true, message: '请输入图片名称!'}]}>
                                <Input placeholder="图片名称可用于搜索" style={{width: 328}}/>
                              </ProForm.Item>

                              <ProForm.Item name="image_size" label="" style={{display: 'none'}}>
                                <Input placeholder="仅内部可见,方便整理和查看"/>
                              </ProForm.Item>

                              <ProForm.Item
                                name='image_picurl'
                                label={'上传图片'}
                              >
                                {/* 上传图片 */}
                                <Uploader
                                  fileType={'formImage'}
                                  customRequest={async (req) => {
                                    try {
                                      const file = req.file as RcFile;
                                      const getUploadUrlRes = await GetSignedUrl({file_name: file.name})
                                      if (getUploadUrlRes.code !== 0) {
                                        message.error('获取上传地址失败');
                                        return;
                                      }
                                      const uploadRes = await fetch(getUploadUrlRes?.data?.upload_url, {
                                        method: 'PUT',
                                        body: file
                                      });
                                      if (uploadRes.clone().ok) {
                                        const reply_details = formRef?.current?.getFieldValue('reply_details');
                                        reply_details[currentKey].image_picurl = getUploadUrlRes?.data?.download_url;
                                        reply_details[currentKey].image_title = file.name;
                                        reply_details[currentKey].image_size = String(file.size);
                                        formRef?.current?.setFieldsValue({...reply_details})
                                        return;
                                      }
                                      message.error('上传图片失败');
                                      return;
                                    } catch (e) {
                                      message.error('上传图片失败');
                                    }
                                  }}
                                />
                              </ProForm.Item>
                            </div>
                          }
                          if (content_type === 4) {// 解析链接
                            return (
                              <div>
                                <ProFormText
                                  name='link_url'
                                  label='链接地址'
                                  placeholder="请输入链接,链接地址以http或https开头"
                                  fieldProps={{
                                    addonAfter: (
                                      <Tooltip title="点击抓取远程链接,自动填充标题,描述,图片">
                                        <div
                                          onClick={async () => {
                                            const res = await ParseURL(formRef?.current?.getFieldValue('reply_details')[currentKey].link_url)
                                            if (res.code !== 0) {
                                              message.error(res.message);
                                            } else {
                                              message.success('解析链接成功');
                                              const reply_details = formRef?.current?.getFieldValue('reply_details');
                                              reply_details[currentKey].link_title = res.data.title;// 链接标题
                                              reply_details[currentKey].link_desc = res.data.desc; // 链接描述
                                              reply_details[currentKey].link_picurl = res.data.img_url; // 图片
                                              formRef?.current?.setFieldsValue({reply_details})
                                            }
                                          }}
                                          style={{
                                            cursor: "pointer",
                                            width: 32,
                                            height: 30,
                                            display: 'flex',
                                            alignItems: 'center',
                                            justifyContent: 'center'
                                          }}>
                                          <SyncOutlined/>
                                        </div>
                                      </Tooltip>
                                    )
                                  }}
                                  rules={[
                                    {
                                      required: true,
                                      message: '请输入链接地址',
                                    },
                                    {
                                      type: 'url',
                                      message: '请填写正确的的URL,必须是http或https开头',
                                    },
                                  ]}
                                />
                                <ProFormText
                                  name='link_title'
                                  label='链接标题'
                                  width='md'
                                  rules={[
                                    {
                                      required: true,
                                      message: '请输入链接标题',
                                    },
                                  ]}
                                />
                                <ProFormTextArea
                                  name='link_desc'
                                  label='链接描述'
                                  width='md'
                                />
                                <ProForm.Item
                                  label='链接封面'
                                  name='link_picurl'
                                  rules={[
                                    {
                                      required: true,
                                      message: '请上传链接图片!',
                                    },
                                  ]}
                                >
                                  <Uploader
                                    fileType={'formImage'}
                                    customRequest={async (req) => {
                                      try {
                                        const file = req.file as RcFile;
                                        const getUploadUrlRes = await GetSignedUrl({file_name: file.name})
                                        if (getUploadUrlRes.code !== 0) {
                                          message.error('获取上传地址失败');
                                          return;
                                        }
                                        const uploadRes = await fetch(getUploadUrlRes?.data?.upload_url, {
                                          method: 'PUT',
                                          body: file
                                        });
                                        if (uploadRes.clone().ok) {
                                          const reply_details = formRef?.current?.getFieldValue('reply_details');
                                          reply_details[currentKey].link_picurl = getUploadUrlRes?.data?.download_url;
                                          formRef?.current?.setFieldsValue({...reply_details})
                                          return;
                                        }
                                        message.error('上传图片失败');
                                        return;
                                      } catch (e) {
                                        message.error('上传图片失败');
                                      }
                                    }}
                                  />
                                </ProForm.Item>
                              </div>
                            )
                          }
                          if (content_type === 5) {
                            return (
                              <div>
                                <ProForm.Item name="pdf_title" label="" style={{display: 'none'}}>
                                  <Input/>
                                </ProForm.Item>
                                <ProForm.Item name="pdf_size" label="" style={{display: 'none'}}>
                                  <Input/>
                                </ProForm.Item>
                                <div className={styles.pdfUploadBox}>
                                  <ProForm.Item name={'pdf_fileurl'}>
                                    <Uploader
                                      fileType='PDF'
                                      fileInfoAry={fileInfoAry}
                                      setFileInfoAry={setFileInfoAry}
                                      currentKey={currentKey}
                                      initialFileInfo={{
                                        // 修改PDF时要回显的文件信息
                                        fileName: formRef?.current?.getFieldValue('reply_details')[currentKey].pdf_title,
                                        fileSize: formRef?.current?.getFieldValue('reply_details')[currentKey].pdf_size,
                                        key: currentKey
                                      }}
                                      customRequest={async (req) => {
                                        try {
                                          const file = req.file as RcFile;
                                          const getUploadUrlRes = await GetSignedUrl({file_name: file.name})
                                          if (getUploadUrlRes.code !== 0) {
                                            message.error('获取上传地址失败');
                                            return;
                                          }
                                          // 上传
                                          const uploadRes = await fetch(getUploadUrlRes?.data?.upload_url, {
                                            method: 'PUT',
                                            body: file
                                          });
                                          // 下载
                                          if (uploadRes.clone().ok) {
                                            const reply_details = formRef?.current?.getFieldValue('reply_details');
                                            reply_details[currentKey].pdf_fileurl = getUploadUrlRes?.data?.download_url;
                                            reply_details[currentKey].pdf_title = file.name;
                                            reply_details[currentKey].pdf_size = String(file.size);
                                            formRef?.current?.setFieldsValue({reply_details})
                                            return;
                                          }
                                          message.error('上传PDF失败');
                                          return;
                                        } catch (e) {
                                          message.error('上传PDF失败');
                                        }
                                      }}
                                    />
                                  </ProForm.Item>
                                </div>
                              </div>
                            );
                          }
                          if (content_type === 6) {
                            return (
                              <>
                                <Row>
                                  <div style={{display: 'none'}}>
                                    <ProFormText name="video_title"/>
                                    <ProFormText name="video_size"/>
                                  </div>
                                  <div className={styles.videoUplaodBox}></div>
                                  <ProForm.Item name={'video_picurl'}>
                                    <Uploader
                                      fileType='视频'
                                      customRequest={async (req) => {
                                        try {
                                          const file = req.file as RcFile;
                                          const getUploadUrlRes = await GetSignedUrl({file_name: file.name})
                                          if (getUploadUrlRes.code !== 0) {
                                            message.error('获取上传地址失败');
                                            return;
                                          }
                                          // 上传
                                          const uploadRes = await fetch(getUploadUrlRes?.data?.upload_url, {
                                            method: 'PUT',
                                            body: file
                                          });
                                          // 下载
                                          if (uploadRes.clone().ok) {
                                            const reply_details = formRef?.current?.getFieldValue('reply_details');
                                            reply_details[currentKey].video_picurl = getUploadUrlRes?.data?.download_url;
                                            reply_details[currentKey].video_title = file.name;
                                            reply_details[currentKey].video_size = String(file.size);
                                            formRef?.current?.setFieldsValue({reply_details})
                                            return;
                                          }
                                          message.error('上传视频失败');
                                          return;
                                        } catch (e) {
                                          message.error('上传视频失败');
                                        }
                                      }}
                                    />
                                  </ProForm.Item>
                                </Row>
                              </>
                            );
                          }
                          return <></>;
                        }}
                      </ProFormDependency>
                    </div>
                  </div>
                  <div>
                    {
                      formRef?.current?.getFieldValue('reply_details').length>1 &&  <Tooltip key={'remove'} title="删除">
                        <DeleteTwoTone
                          style={{paddingTop: 8}}
                          className={'ant-pro-form-list-action-icon'}
                          onClick={() => {
                            const temp = [...fileInfoAry]
                            for (let i = 0; i < temp.length; i += 1) {
                              if (temp[i].key === currentKey) {
                                temp.splice(i, 1)
                              }
                            }
                            setFileInfoAry(temp)
                            if(formRef?.current?.getFieldValue('reply_details')?.[currentKey].id){
                              setDeletedIds([...deletedIds,formRef?.current?.getFieldValue('reply_details')[currentKey].id])
                            }
                            operation.remove(currentKey);
                          }}

                        />
                      </Tooltip>
                    }
                  </div>
                </div>
              )
            }}
          >
          </ProFormList>
        </ProForm>
      </Modal>
      <GroupModal
        allDepartments={props.allDepartments as DepartmentOption[]}
        ref={groupModalRef}
        visible={groupModalVisible}
        setVisible={setGroupModalVisible}
        onFinish={async (values, action) => {
          if (action === 'create') {
            await HandleRequest({...values, sub_groups: values.sub_group || []}, CreateEnterpriseScriptGroups, () => {
              setGroupItemsTimestamp(Date.now)
              groupModalRef.current.close()
            })
          } else {
            await HandleRequest({...values, sub_groups: values.sub_group || []}, UpdateEnterpriseScriptGroups, () => {
              setGroupItemsTimestamp(Date.now)
              groupModalRef.current.close()
            })
          }
        }}
      />
    </div>

  )
}
Example #16
Source File: index.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
Material: React.FC<MaterialProps> = (props) => {
  const [materalList, setMateralList] = useState<Material.Item[]>([])
  const [filterVisible, setFilterVisible] = useState<boolean>(false)
  const [modalFormVisible, setModalFormVisible] = useState<boolean>(false)
  const [targetUpdateMaterial, setTargetUpdateMaterial] = useState<Material.Item>({} as Material.Item)
  const [targetDeleteMaterial, setTargetDeleteMaterial] = useState<Material.Item>({} as Material.Item)
  const [tagModalVisible, setTagModalVisible] = useState(false)
  const [choosedTags, setChoosedTags] = useState<MaterialTag.Item[]>([])// 新增&修改素材时选择的标签
  const [searchTags, setSearchTags] = useState<MaterialTag.Item[]>([])
  const realSearchTags = useRef<MaterialTag.Item[]>([])
  const [tagsForFilter, setTagsForFilter] = useState<MaterialTag.Item[]>([]) // 筛选标签的dropdown里的所有标签
  const [keyword, setKeyword] = useState<string>('');
  const [showSearchTags, setShowSearchTags] = useState(false)
  const [linkFetching, setLinkFetching] = useState(false);
  const [fileInfo, setFileInfo] = useState({fileName: '', fileSize: ''})
  const [materialLoading, setMaterialLoading] = useState(true)
  const modalFormRef = useRef<FormInstance>()

  const {allTags, allTagsMap} = useContext(TagContext)

  useEffect(() => {
    const filteredTags = allTags.filter((tag) => {
      if (keyword.trim() === '') {
        return true;
      }
      if (tag.name?.includes(keyword)) {
        return true;
      }
      return false;

    });
    setTagsForFilter(filteredTags);
  }, [allTags, keyword])

  // 查询素材列表
  const queryMaterialList = (material_tag_list?: string[], title?: string) => {
    QueryMaterialList({
      page_size: 5000,
      material_type: fileTypeMap[props.fileType].material_type,
      material_tag_list,
      title
    }).then(res => {
      setMaterialLoading(false)
      if (res?.code === 0 && res?.data) {
        setMateralList(res?.data?.items || [])
      } else {
        message.error(res?.message)
      }
    })
  }
  useEffect(() => {
    setMaterialLoading(true)
    queryMaterialList()
    realSearchTags.current = []
    setSearchTags([])
  }, [props.fileType, allTags])

  // 处理修改 删除的目标素材
  const operateMaterial = (targetMaterial: Material.Item, operation: string) => {
    setFileInfo({fileName: targetMaterial.title, fileSize: targetMaterial.file_size})
    if (operation === 'update') {
      setModalFormVisible(true)
      setTargetUpdateMaterial(targetMaterial)
      setChoosedTags(targetMaterial?.material_tag_list?.map((tagId: string) =>
        allTagsMap[tagId]
      ))
      setTimeout(() => {
        modalFormRef.current?.setFieldsValue({
          ...targetMaterial,
          file_url: targetMaterial.url,
          file_size: targetMaterial.file_size
        })
      }, 100)
    }
    if (operation === 'delete') {
      setTargetDeleteMaterial(targetMaterial)
    }
  }

  return (
    <TagContext.Consumer>
      {
        (contextValue) => (
          <div>
            <div>
              <div className={styles.topNav}>
                <div className={styles.topNavTitle}>
                  {props.fileType}素材(共{materalList?.length}篇)
                </div>
                <div className={styles.topNavOperator}>
                  <Input.Search placeholder={`搜索${props.fileType}标题`} style={{width: 300}} onSearch={(value) => {
                    queryMaterialList(realSearchTags.current.map(tag => tag?.id), value)
                  }}/>
                  <Dropdown
                    visible={filterVisible}
                    overlay={
                      <div className={styles.overlay}>
                        <div className={styles.overlayTitle}>素材标签 ( {contextValue.allTags.length} )</div>
                        {/* 筛选标签 */}
                        <Form
                          layout={'horizontal'}
                        >
                          <Input
                            allowClear={true}
                            placeholder={'输入关键词搜索标签'}
                            value={keyword}
                            onChange={(e) => {
                              setKeyword(e.currentTarget.value)
                            }}
                            style={{width: 320, marginLeft: 10}}
                          />
                          <div style={{padding: "14px 4px"}}>
                            {tagsForFilter?.map((tag) => {
                              const isSelected = searchTags.map((searchTag) => searchTag.id)?.includes(tag?.id);
                              return (
                                <Space direction={'horizontal'} wrap={true}>
                                  <Tag
                                    className={`tag-item ${isSelected ? ' selected-tag-item' : ''}`}
                                    style={{cursor: 'pointer', margin: '6px'}}
                                    key={tag.id}
                                    onClick={() => {
                                      if (tag?.id && isSelected) {
                                        setSearchTags(searchTags.filter((searchTag) => {
                                          return searchTag.id !== tag?.id
                                        }))
                                      } else {
                                        setSearchTags([...searchTags, tag])
                                      }
                                    }}
                                  >
                                    {tag.name}
                                  </Tag>
                                </Space>
                              )
                            })}
                          </div>
                          {contextValue.allTags?.length === 0 &&
                          <Empty style={{marginTop: 36, marginBottom: 36}} image={Empty.PRESENTED_IMAGE_SIMPLE}/>}
                          <div style={{display: 'flex', justifyContent: 'flex-end'}}>
                            <Button onClick={() => setFilterVisible(false)}>取消</Button>
                            <Button
                              style={{marginLeft: 6}}
                              type='primary'
                              htmlType="submit"
                              onClick={() => {
                                setFilterVisible(false)
                                setShowSearchTags(true)
                                realSearchTags.current = searchTags
                                queryMaterialList(realSearchTags.current.map(tag => tag?.id) || [])
                              }}>完成</Button>
                          </div>
                        </Form>
                      </div>
                    } trigger={['click']}>
                    <div>
                      <Button
                        style={{margin: '0px 6px'}}
                        onClick={() => {
                          setFilterVisible(!filterVisible)
                        }}>筛选</Button>
                    </div>
                  </Dropdown>
                  <Button type={'primary'} onClick={() => {
                    setModalFormVisible(true)
                  }}>添加{props.fileType}</Button>
                </div>
              </div>
            </div>
            <div>
              {
                realSearchTags.current.length > 0 && showSearchTags ? <div className={styles.filterTagBox}>
                    {
                      realSearchTags.current.map(tag =>
                          <Tag
                            key={tag.id}
                            className={'tag-item selected-tag-item'}
                          >
                            {tag.name}
                            <span>
                        &nbsp;&nbsp;
                              <CloseOutlined
                                style={{fontSize: '12px', cursor: 'pointer'}}
                                onClick={() => {
                                  realSearchTags.current = realSearchTags.current.filter((t) => {
                                    return t.id !== tag?.id
                                  })
                                  setSearchTags(realSearchTags.current)
                                  queryMaterialList(realSearchTags.current.map(t => t?.id) || [])
                                }}
                              />
                      </span>
                          </Tag>
                      )
                    }
                    <Button
                      type={'link'}
                      icon={<ClearOutlined/>}
                      style={{display: (showSearchTags && realSearchTags.current.length > 0) ? 'inline-block' : 'none'}}
                      onClick={() => {
                        setShowSearchTags(false)
                        setSearchTags([])
                        queryMaterialList()
                      }}>清空筛选</Button>
                  </div>
                  :
                  <div style={{margin: 0}}/>
              }
              {/* 素材列表 */}
              <Spin spinning={materialLoading} style={{marginTop:50}}>
                <div className={styles.articles}>
                  {
                    materalList?.map((item) => <MaterialCard {...item} callback={operateMaterial}/>)
                  }
                </div>
              </Spin>
              {
                materalList?.length === 0 && !materialLoading && <Empty style={{marginTop: 100}} image={Empty.PRESENTED_IMAGE_SIMPLE}/>
              }
            </div>
            {/* 修改素材弹窗 */}
            <ModalForm
              formRef={modalFormRef}
              className={'dialog from-item-label-100w'}
              layout={'horizontal'}
              width={'560px'}
              visible={modalFormVisible}
              onVisibleChange={(visible) => {
                if (!visible) {
                  setTargetUpdateMaterial({} as Material.Item)
                  setChoosedTags([])
                }
                setModalFormVisible(visible)
                modalFormRef.current?.resetFields()
              }}
              // @ts-ignore
              onFinish={(params) => {
                if (targetUpdateMaterial.id) {
                  const tagIdList = choosedTags.map(tag => {
                    return tag?.id
                  })
                  UpdateMaterial({
                    material_type: fileTypeMap[props.fileType].material_type, ...params,
                    id: targetUpdateMaterial.id,
                    material_tag_list: choosedTags.length > 0 ? tagIdList : [],
                  })
                    .then(res => {
                      if (res?.code === 0) {
                        message.success(`修改${props.fileType}成功`)
                        unstable_batchedUpdates(() => {
                          queryMaterialList(searchTags.map(tag => tag?.id) || [])
                          setModalFormVisible(false)
                          setChoosedTags([])
                          setTargetUpdateMaterial({} as Material.Item)
                        })
                      } else {
                        message.error(res.message)
                      }
                    })
                } else {
                  const tagIdList = choosedTags.map(tag => {
                    return tag?.id
                  })
                  CreateMaterial({
                    material_type: fileTypeMap[props.fileType].material_type, ...params,
                    material_tag_list: choosedTags.length > 0 ? tagIdList : [],
                  })
                    .then(res => {
                      if (res?.code === 0) {
                        message.success(`新增${props.fileType}成功`)
                        unstable_batchedUpdates(() => {
                          queryMaterialList(searchTags.map(tag => tag?.id) || [])
                          setModalFormVisible(false)
                          setChoosedTags([])
                        })
                      } else {
                        message.error(res.message)
                      }
                    })
                }
              }}
            >
              {
                // 修改链接素材 弹窗内容
                props.fileType === '链接' && <div key={props.fileType}>
                  <Spin spinning={linkFetching}>
                    <h2 className='dialog-title'> {targetUpdateMaterial.id ? '修改链接' : '添加链接'} </h2>
                    <ProForm.Item initialValue={'link'} name={'msgtype'} noStyle={true}>
                      <input type={'hidden'}/>
                    </ProForm.Item>
                    <ProFormText
                      name='link'
                      label='链接地址'
                      placeholder="链接地址以http(s)开头"
                      width='md'
                      fieldProps={{
                        disabled: linkFetching,
                        addonAfter: (
                          <Tooltip title="点击抓取远程链接,自动填充标题,描述,图片">
                            <div
                              onClick={async () => {
                                setLinkFetching(true);
                                const res = await ParseURL(modalFormRef.current?.getFieldValue('link'))
                                setLinkFetching(false);
                                if (res.code !== 0) {
                                  message.error(res.message);
                                } else {
                                  message.success('解析链接成功');
                                  modalFormRef?.current?.setFieldsValue({
                                    customer_link_enable: 1,
                                    title: res.data.title,
                                    digest: res.data.desc,
                                    file_url: res.data.img_url,
                                  })
                                }
                              }}
                              style={{
                                cursor: "pointer",
                                width: 32,
                                height: 30,
                                display: 'flex',
                                alignItems: 'center',
                                justifyContent: 'center'
                              }}>
                              <SyncOutlined/>
                            </div>
                          </Tooltip>
                        )
                      }}
                      rules={[
                        {
                          required: true,
                          message: '请输入链接地址',
                        },
                        {
                          type: 'url',
                          message: '请填写正确的的URL,必须是http或https开头',
                        },
                      ]}
                    />
                    <ProFormText
                      name='title'
                      label='链接标题'
                      width='md'
                      rules={[
                        {
                          required: true,
                          message: '请输入链接标题',
                        },
                      ]}
                    />
                    <ProFormTextArea
                      name='digest'
                      label='链接描述'
                      width='md'
                    />
                    <Form.Item
                      label='链接封面'
                      name='file_url'
                      rules={[
                        {
                          required: true,
                          message: '请上传链接图片!',
                        },
                      ]}
                    >
                      <Uploader
                        fileType='formImage'
                        fileInfo={fileInfo}
                        setFileInfo={setFileInfo}
                        customRequest={async (req) => {
                          try {
                            const file = req.file as RcFile;
                            const getUploadUrlRes = await GetSignedUrl({file_name: file.name})
                            if (getUploadUrlRes.code !== 0) {
                              message.error('获取上传地址失败');
                              return;
                            }
                            // 上传
                            const uploadRes = await fetch(getUploadUrlRes?.data?.upload_url, {
                              method: 'PUT',
                              body: file
                            });
                            if (uploadRes.clone().ok) {
                              modalFormRef?.current?.setFieldsValue({
                                file_url: getUploadUrlRes?.data?.download_url,
                                file_size: String(file.size)
                              })
                              return;
                            }
                            message.error('上传图片失败');
                            return;
                          } catch (e) {
                            message.error('上传图片失败');
                          }
                        }}
                      />
                    </Form.Item>
                    <div style={{display: 'none'}}>
                      <ProFormText name='file_size'/>
                    </div>
                  </Spin>
                </div>
              }
              {
                // 修改海报素材 弹窗内容
                props.fileType === '海报' && <div key={props.fileType}>
                  <h2 className='dialog-title'> {targetUpdateMaterial.id ? '修改海报' : '添加海报'} </h2>
                  <ProForm.Item
                    name='file_url'
                  >
                    <Uploader
                      fileType='海报'
                      fileInfo={fileInfo}
                      setFileInfo={setFileInfo}
                      customRequest={async (req) => {
                        try {
                          const file = req.file as RcFile;
                          const getUploadUrlRes = await GetSignedUrl({file_name: file.name})
                          if (getUploadUrlRes.code !== 0) {
                            message.error('获取上传地址失败');
                            return;
                          }
                          // 上传
                          const uploadRes = await fetch(getUploadUrlRes?.data?.upload_url, {
                            method: 'PUT',
                            body: file
                          });
                          if (uploadRes.clone().ok) {
                            modalFormRef?.current?.setFieldsValue({
                              file_url: getUploadUrlRes?.data?.download_url,
                              file_size: String(file.size),
                              title: file.name
                            })
                            return;
                          }
                          message.error('上传图片失败');
                          return;
                        } catch (e) {
                          message.error('上传图片失败');
                        }
                      }}
                    />
                  </ProForm.Item>
                  <div style={{display: 'none'}}>
                    <ProFormText name='file_size'/>
                  </div>
                  <div style={{display: 'none'}}>
                    {/* 文件名 */}
                    <ProFormText name='title'/>
                  </div>
                </div>
              }
              {
                // 修改视频素材 弹窗内容
                props.fileType === '视频' && <div key={props.fileType}>
                  <h2 className='dialog-title'> {targetUpdateMaterial.id ? '修改视频' : '添加视频'} </h2>
                  <ProForm.Item
                    name='file_url'
                  >
                    <Uploader
                      fileType='视频'
                      fileInfo={fileInfo}
                      setFileInfo={setFileInfo}
                      customRequest={async (req) => {
                        try {
                          const file = req.file as RcFile;
                          const getUploadUrlRes = await GetSignedUrl({file_name: file.name})
                          if (getUploadUrlRes.code !== 0) {
                            message.error('获取上传地址失败');
                            return;
                          }
                          // 上传
                          const uploadRes = await fetch(getUploadUrlRes?.data?.upload_url, {
                            method: 'PUT',
                            body: file
                          });
                          if (uploadRes.clone().ok) {
                            modalFormRef?.current?.setFieldsValue({
                              file_url: getUploadUrlRes?.data?.download_url,
                              file_size: String(file.size),
                              title: file.name
                            })
                            return;
                          }
                          message.error('上传视频失败');
                          return;
                        } catch (e) {
                          message.error('上传视频失败');
                        }
                      }}
                    />
                  </ProForm.Item>
                  <div style={{display: 'none'}}>
                    <ProFormText name='file_size'/>
                  </div>
                  <div style={{display: 'none'}}>
                    {/* 文件名 */}
                    <ProFormText name='title'/>
                  </div>
                </div>
              }
              {
                // 修改文件类素材 弹窗内容
                (props.fileType === 'PDF' || props.fileType === 'PPT' || props.fileType === '文档' || props.fileType === '表格') &&
                <div key={props.fileType}>
                  <h2
                    className='dialog-title'> {targetUpdateMaterial.id ? `修改${props.fileType}` : `添加${props.fileType}`} </h2>
                  <ProForm.Item
                    name='file_url'
                  >
                    <Uploader
                      fileType={props.fileType}
                      fileInfo={fileInfo}
                      setFileInfo={setFileInfo}
                      customRequest={async (req) => {
                        try {
                          const file = req.file as RcFile;
                          const getUploadUrlRes = await GetSignedUrl({file_name: file.name})
                          if (getUploadUrlRes.code !== 0) {
                            message.error('获取上传地址失败');
                            return;
                          }
                          // 上传
                          const uploadRes = await fetch(getUploadUrlRes?.data?.upload_url, {
                            method: 'PUT',
                            body: file
                          });
                          if (uploadRes.clone().ok) {
                            modalFormRef?.current?.setFieldsValue({
                              file_url: getUploadUrlRes?.data?.download_url,
                              file_size: String(file.size),
                              title: file.name
                            })
                            return;
                          }
                          message.error(`上传${props.fileType}失败`);
                          return;
                        } catch (e) {
                          message.error(`上传${props.fileType}失败`);
                        }
                      }}
                    />
                  </ProForm.Item>
                  <div style={{display: 'none'}}>
                    <ProFormText name='file_size'/>
                  </div>
                  <div style={{display: 'none'}}>
                    {/* 文件名 */}
                    <ProFormText name='title'/>
                  </div>
                </div>
              }

              <div className={styles.modalTagBox}>
                <Space direction={'horizontal'} wrap={true}>
                  <Button icon={<PlusOutlined/>} onClick={() => setTagModalVisible(true)}>选择标签</Button>
                  {
                    choosedTags?.length > 0 && choosedTags?.map((tag) =>
                      <Tag
                        key={tag?.id}
                        className={'tag-item selected-tag-item'}
                      >
                        {tag?.name}
                        <span>
                        &nbsp;&nbsp;
                          <CloseOutlined
                            style={{fontSize: '12px', cursor: 'pointer'}}
                            onClick={() => {
                              setChoosedTags(choosedTags?.filter((choosedTag) => {
                                return choosedTag?.id !== tag?.id
                              }))
                            }}
                          />
                     </span>
                      </Tag>
                    )}
                </Space>
              </div>
            </ModalForm>
            {/* 删除素材 */}
            <Modal
              visible={!!targetDeleteMaterial.id}
              onOk={() => {
                DeleteMaterial({ids: [targetDeleteMaterial.id]}).then(res => {
                  if (res?.code === 0) {
                    message.success('删除素材标签成功')
                    // setListTimestamp(Date.now)
                    queryMaterialList(searchTags?.map(tag => tag?.id) || [])
                  } else {
                    message.success('删除失败')
                  }
                  setTargetDeleteMaterial({} as Material.Item)
                })
              }}
              onCancel={() => {
                setTargetDeleteMaterial({} as Material.Item)
              }}
            >
              <h3>提示</h3>
              <h4>确定删除「{(targetDeleteMaterial as Material.Item).title}」这个素材吗?删除后不可恢复</h4>
            </Modal>
            {/* 选择素材标签弹窗 */}
            <TagModal
              width={560}
              isEditable={false}
              defaultCheckedTags={() => {
                if (choosedTags?.length > 0) {
                  return choosedTags
                }
                const tempArr: MaterialTag.Item[] = []
                targetUpdateMaterial?.material_tag_list?.forEach((tagId: string) => {
                  tempArr.push(contextValue.allTagsMap[tagId])
                });
                return tempArr || []
              }}
              allTags={contextValue.allTags}
              setAllTags={contextValue.setAllTags}
              visible={tagModalVisible}
              setVisible={setTagModalVisible}
              onCancel={() => {
                setChoosedTags([])
              }}
              reloadTags={contextValue.setTagsItemsTimestamp}
              onFinish={async (values) => {
                setChoosedTags(values)
              }}
            />
          </div>
        )
      }
    </TagContext.Consumer>

  );
}
Example #17
Source File: index.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
CustomerGroupsListList: React.FC = () => {
  const [exportLoading, setExportLoading] = useState<boolean>(false);
  const [extraFilterParams, setExtraFilterParams] = useState<any>();
  const [allStaffs, setAllStaffs] = useState<StaffOption[]>([]);
  const actionRef = useRef<ActionType>();
  const queryFormRef = useRef<FormInstance>();
  const [syncLoading, setSyncLoading] = useState<boolean>(false);
  const [allTagGroups, setAllTagGroups] = useState<GroupChatTagGroupItem[]>([]);
  const [selectedItems, setSelectedItems] = useState<GroupChatItem[]>([]);
  const [batchTagModalVisible, setBatchTagModalVisible] = useState<boolean>(false);

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

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


  const formattedParams = (originParams: any) => {
    const params = { ...originParams, ...extraFilterParams };

    if (params.tag_list) {
      params.group_tag_ids = params.tag_list;
      delete params.tag_list;
    }

    if (params.create_time) {
      [params.create_time_start, params.create_time_end] = params.create_time;
      delete params.create_time;
    }

    return params;
  };

  const columns: ProColumns<GroupChatItem>[] = [
    {
      title: '群名称',
      dataIndex: 'name',
      valueType: 'text',
      fixed:'left',
      render: (dom, item) => {
        return (
          <div className={'tag-like-item'}>
            <img className={'icon'} src={GroupChatIcon} />
            <span className={'text'}>{item.name}</span>
          </div>
        );
      },
    },
    {
      title: '群主',
      dataIndex: 'owners',
      valueType: 'select',
      renderFormItem: () => {
        return (
          <StaffTreeSelect options={allStaffs} maxTagCount={4} />
        );
      },
      render: (__, item) => {
        return (
          <div className={'tag-like-item'}>
            <img className={'icon'} src={item.owner_avatar_url} />
            <span className={'text'}>{item.owner}</span>
          </div>
        );
      },
    },

    {
      title: '群标签',
      dataIndex: 'group_tag_ids',
      valueType: 'text',
      renderFormItem: () => {
        return (
          <GroupChatTagSelect isEditable={false} allTagGroups={allTagGroups} maxTagCount={6} />
        );
      },
      render: (dom, item) => {
        return <CollapsedTags limit={6} tags={item.tags} />;
      },
    },
    {
      title: '群聊状态',
      dataIndex: 'status',
      valueType: 'select',
      hideInTable: true,
      hideInSearch: false,
      valueEnum: {
        0: '未解散',
        1: '已解散',
      },
    },

    {
      title: '群人数',
      dataIndex: 'total',
      valueType: 'digit',
      hideInSearch: true,
      sorter: true,
      render: (dom, item) => {
        return <span>{item.total}</span>;
      },
    },
    {
      title: '当日入群',
      dataIndex: 'today_join_member_num',
      valueType: 'digit',
      hideInSearch: true,
      sorter: true,
      render: (dom, item) => {
        return <span>{item.today_join_member_num}</span>;
      },
    },
    {
      title: '当日退群',
      dataIndex: 'today_quit_member_num',
      valueType: 'digit',
      hideInSearch: true,
      sorter: true,
      render: (dom, item) => {
        return <span>{item.today_quit_member_num}</span>;
      },
    },

    {
      title: '创群时间',
      dataIndex: 'create_time',
      valueType: 'dateRange',
      hideInSearch: false,
      sorter: true,
      filtered: true,
      render: (dom, item) => {
        return (
          <div
            dangerouslySetInnerHTML={{
              __html: moment(item.create_time).format('YYYY-MM-DD HH:mm').split(' ').join('<br/>'),
            }}
          />
        );
      },
    },
    {
      title: '群ID',
      dataIndex: 'ext_chat_id',
      valueType: 'text',
      hideInSearch: true,
      render: (dom, item) => {
        return (
          <div className={'tag-like-item'}>
            <span className={'text'}>{item.ext_chat_id}</span>
          </div>
        );
      },
    },
  ];

  return (
    <PageContainer
      fixedHeader
      header={{
        title: '客户群列表',
      }}
      extra={[
        <Button
          key='export'
          type='dashed'
          loading={exportLoading}
          icon={<CloudDownloadOutlined style={{ fontSize: 16, verticalAlign: '-3px' }} />}
          onClick={async () => {
            setExportLoading(true);
            try {
              const content = await ExportCustomerGroupsList(
                formattedParams(queryFormRef.current?.getFieldsValue()),
              );
              const blob = new Blob([content], {
                type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
              });
              FileSaver.saveAs(blob, `客户群列表.xlsx`);
            } catch (e) {
              console.log(e);
              message.error('导出失败');
            }
            setExportLoading(false);
          }}
        >
          导出Excel
        </Button>,
        <Button
          key={'sync'}
          type='dashed'
          icon={<SyncOutlined style={{ fontSize: 16, verticalAlign: '-3px' }} />}
          loading={syncLoading}
          onClick={async () => {
            setSyncLoading(true);
            const res: CommonResp = await Sync();
            if (res.code === 0) {
              setSyncLoading(false);
              message.success('同步成功');
            } else {
              setSyncLoading(false);
              message.error(res.message);
            }
          }}
        >
          同步数据
        </Button>,
      ]}
    >
      <ProTable<GroupChatItem>
        formRef={queryFormRef}
        actionRef={actionRef}
        className={'table'}
        scroll={{ x: 'max-content' }}
        columns={columns}
        rowKey='id'
        pagination={{
          pageSizeOptions: ['5', '10', '20', '50', '100'],
          pageSize: 5,
        }}
        onReset={() => {
          setExtraFilterParams({});
        }}
        toolBarRender={false}
        bordered={false}
        tableAlertRender={false}
        params={{}}
        request={async (originParams: any, sort, filter) => {
          return ProTableRequestAdapter(
            formattedParams(originParams),
            sort,
            filter,
            QueryCustomerGroupsList,
          );
        }}
        dateFormatter='string'
        rowSelection={{
          onChange: (__, items) => {
            setSelectedItems(items);
          },
        }}
      />

      {selectedItems?.length > 0 && (
        // 底部选中条目菜单栏
        <FooterToolbar>
          <span>
            已选择 <a style={{ fontWeight: 600 }}>{selectedItems.length}</a> 项 &nbsp;&nbsp;
          </span>
          <Divider type='vertical' />
          <Button
            icon={<TagOutlined />}
            type={'dashed'}
            onClick={() => {
              setBatchTagModalVisible(true);
            }}
          >
            批量打标签
          </Button>

        </FooterToolbar>
      )}


      <GroupChatTagSelectionModal
        width={'630px'}
        visible={batchTagModalVisible}
        setVisible={setBatchTagModalVisible}
        onFinish={async (selectedTags) => {
          const selectedTagIDs = selectedTags.map((selectedTag) => selectedTag.id);
          const selectedGroupChatIDs = selectedItems.map((groupChat) => groupChat.id);
          await HandleRequest({
            add_tag_ids: selectedTagIDs,
            group_chat_ids: selectedGroupChatIDs,
          }, UpdateGroupChatTags, () => {
            // @ts-ignore
            actionRef?.current?.reloadAndRest();
            setSelectedItems([]);
          });
        }}
        allTagGroups={allTagGroups}
        isEditable={true}
        withLogicalCondition={false}
      />

    </PageContainer>
  );
}
Example #18
Source File: index.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
CustomerTagGroupList: React.FC = () => {
  const [currentItem, setCurrentItem] = useState<CustomerTagGroupItem>({});
  const [tagGroups, setTagGroups] = useState<CustomerTagGroupItem[]>([]);
  const [createModalVisible, setCreateModalVisible] = useState<boolean>(false);
  const [editModalVisible, setEditModalVisible] = useState<boolean>(false);
  const [syncLoading, setSyncLoading] = useState<boolean>(false);
  const [actionLoading, setActionLoading] = useState<boolean>(false);
  const [inputLoading, setInputLoading] = useState<boolean>(false);
  const [minOrder, setMinOrder] = useState<number>(10000);
  const [maxOrder, setMaxOrder] = useState<number>(100000);
  const [currentInputTagGroupExtID, setCurrentInputTagGroupExtID] = useState<string>();
  const [allDepartments, setAllDepartments] = useState<DepartmentOption[]>([]);
  const [allDepartmentMap, setAllDepartmentMap] = useState<Dictionary<DepartmentOption>>({});
  const queryFilterFormRef = useRef<FormInstance>();

  useEffect(() => {
    QueryDepartment({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);
      }
    });
    queryFilterFormRef.current?.submit();
  }, []);

  // @ts-ignore
  // @ts-ignore
  return (
    <PageContainer
      fixedHeader
      header={{
        title: '客户标签管理',
      }}
      extra={[
        <Button
          key='create'
          type='primary'
          icon={<PlusOutlined style={{fontSize: 16, verticalAlign: '-3px'}}/>}
          onClick={() => {
            setCreateModalVisible(true);
          }}
        >
          添加标签组
        </Button>,

        <Button
          key={'sync'}
          type='dashed'
          icon={<SyncOutlined style={{fontSize: 16, verticalAlign: '-3px'}}/>}
          loading={syncLoading}
          onClick={async () => {
            setSyncLoading(true);
            const res: CommonResp = await Sync();
            if (res.code === 0) {
              setSyncLoading(false);
              message.success('同步成功');
              queryFilterFormRef.current?.submit();
            } else {
              setSyncLoading(false);
              message.error(res.message);
            }
          }}
        >
          同步企业微信标签
        </Button>,
      ]}
    >
      <ProCard className={styles.queryFilter}>
        <QueryFilter
          formRef={queryFilterFormRef}
          onReset={() => {
            queryFilterFormRef.current?.submit();
          }}
          onFinish={async (params: any) => {
            setActionLoading(true);
            const res: CommonResp = await Query({
              ...params,
              page_size: 5000,
              sort_field: 'order',
              sort_type: 'desc',
            });
            setActionLoading(false);
            if (res.code === 0) {
              setTagGroups(res.data.items);
              if (res.data?.items[0]) {
                setMaxOrder(res.data.items[0]?.order);
              }
              if (res.data?.items.length >= 1 && res.data?.items[res.data?.items.length - 1]) {
                let min = res.data?.items[res.data?.items.length - 1];
                min = min - 1 >= 0 ? min - 1 : 0;
                setMinOrder(min);
              }
            } else {
              message.error('查询标签失败');
              setTagGroups([]);
            }
          }}
        >
          <Form.Item label='可用部门' name='ext_department_ids'>
            <DepartmentTreeSelect
              onChange={() => {
                queryFilterFormRef.current?.submit();
              }}
              options={allDepartments}
            />
          </Form.Item>

          <ProFormText width={'md'} name='name' label='搜索' placeholder='请输入关键词'/>

        </QueryFilter>
      </ProCard>

      <ProCard style={{marginTop: 12}} bodyStyle={{paddingTop: 0}} gutter={0}>
        <Spin spinning={actionLoading}>
          {(!tagGroups || tagGroups.length === 0) && <Empty style={{marginTop: 36, marginBottom: 36}}/>}
          {tagGroups && tagGroups.length > 0 && (
            <ReactSortable<any>
              handle={'.draggable-button'}
              className={styles.tagGroupList}
              list={tagGroups}
              setList={setTagGroups}
              swap={true}
              onEnd={async (e) => {
                // @ts-ignore
                const from = tagGroups[e.newIndex];
                // @ts-ignore
                const to = tagGroups[e.oldIndex];
                const res = await ExchangeOrder({id: from.id, exchange_order_id: to.id});
                if (res.code !== 0) {
                  message.error(res.message)
                }
              }}
            >
              {tagGroups.map((tagGroup) => (
                <Row className={styles.tagGroupItem} data-id={tagGroup.id} key={tagGroup.ext_id}>
                  <Col md={4} className={styles.tagName}>
                    <h4>{tagGroup.name}</h4>
                  </Col>
                  <Col md={16} className={styles.tagList}>
                    <Row>
                      可见范围:
                      {tagGroup.department_list && !tagGroup.department_list.includes(0) ? (
                        <Space direction={'horizontal'} wrap={true} style={{marginBottom: 6}}>
                          {tagGroup.department_list.map((id) => (
                            <div key={id}>
                            <span>
                              <FolderFilled
                                style={{
                                  color: '#47a7ff',
                                  fontSize: 20,
                                  marginRight: 6,
                                  verticalAlign: -6,
                                }}
                              />
                              {allDepartmentMap[id]?.name}
                            </span>
                            </div>
                          ))}
                        </Space>
                      ) : (
                        <span>全部员工可见</span>
                      )}
                    </Row>
                    <Row style={{marginTop: 12}}>
                      <Space direction={'horizontal'} wrap={true}>
                        <Button
                          icon={<PlusOutlined/>}
                          onClick={() => {
                            setCurrentInputTagGroupExtID(tagGroup.ext_id);
                          }}
                        >
                          添加
                        </Button>

                        {currentInputTagGroupExtID === tagGroup.ext_id && (
                          <Input
                            autoFocus={true}
                            disabled={inputLoading}
                            placeholder='逗号分隔,回车保存'
                            onBlur={() => setCurrentInputTagGroupExtID('')}
                            onPressEnter={async (e) => {
                              setInputLoading(true);
                              const res = await CreateTag({
                                names: e.currentTarget.value
                                  .replace(',', ',')
                                  .split(',')
                                  .filter((val) => val),
                                ext_tag_group_id: tagGroup.ext_id || '',
                              });
                              if (res.code === 0) {
                                setCurrentInputTagGroupExtID('');
                                tagGroup.tags?.unshift(...res.data);
                              } else {
                                message.error(res.message);
                              }
                              setInputLoading(false);
                            }}
                          />
                        )}
                        {tagGroup.tags?.map((tag) => (
                          <Tag className={styles.tagItem} key={tag.id}>
                            {tag.name}
                          </Tag>
                        ))}
                      </Space>
                    </Row>
                  </Col>
                  <Col md={4} className={styles.groupAction}>
                    <Tooltip title="拖动可实现排序" trigger={['click']}>
                      <Button
                        className={'draggable-button'}
                        icon={<DragOutlined
                          style={{cursor: 'grabbing'}}
                        />}
                        type={'text'}
                      >
                        排序
                      </Button>
                    </Tooltip>

                    <Button
                      icon={<EditOutlined/>}
                      type={'text'}
                      onClick={() => {
                        setCurrentItem(tagGroup);
                        setEditModalVisible(true);
                      }}
                    >
                      修改
                    </Button>
                    <Button
                      icon={<DeleteOutlined/>}
                      type={'text'}
                      onClick={() => {
                        Modal.confirm({
                          title: `删除标签分组`,
                          content: `是否确认删除「${tagGroup.name}」分组?`,
                          okText: '删除',
                          okType: 'danger',
                          cancelText: '取消',
                          onOk() {
                            return HandleRequest({ext_ids: [tagGroup.ext_id]}, Delete, () => {
                              queryFilterFormRef.current?.submit();
                            });
                          },
                        });
                      }}
                    >
                      删除
                    </Button>
                  </Col>
                </Row>
              ))}
            </ReactSortable>
          )}
        </Spin>
      </ProCard>

      <CreateModalForm
        // 创建标签
        type={'create'}
        minOrder={minOrder}
        maxOrder={maxOrder}
        allDepartments={allDepartments}
        setVisible={setCreateModalVisible}
        initialValues={{tags: [{name: ''}], department_list: [0]}}
        visible={createModalVisible}
        onFinish={async (values) => {
          await HandleRequest(values, Create, () => {
            queryFilterFormRef.current?.submit();
            setCreateModalVisible(false);
          });
        }}
      />

      <CreateModalForm
        // 修改标签
        type={'edit'}
        destroyOnClose={true}
        minOrder={minOrder}
        maxOrder={maxOrder}
        allDepartments={allDepartments}
        setVisible={setEditModalVisible}
        visible={editModalVisible}
        initialValues={currentItem}
        onFinish={async (values) => {
          await HandleRequest(values, Update, () => {
            queryFilterFormRef.current?.submit();
            setEditModalVisible(false);
          });
        }}
      />
    </PageContainer>
  );
}
Example #19
Source File: index.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
CustomerList: React.FC = () => {
  const [exportLoading, setExportLoading] = useState<boolean>(false);
  const [extraFilterParams] = useState<any>();// setExtraFilterParams
  const [allStaffs, setAllStaffs] = useState<StaffOption[]>([]);
  const [staffMap, setStaffMap] = useState<Dictionary<StaffOption>>({});
  const actionRef = useRef<ActionType>();
  const queryFormRef = useRef<FormInstance>();
  const [syncLoading, setSyncLoading] = useState<boolean>(false);
  const [selectedItems, setSelectedItems] = useState<CustomerItem[]>([]);
  const [allTagGroups, setAllTagGroups] = useState<CustomerTagGroupItem[]>([]);
  const [batchTagModalVisible, setBatchTagModalVisible] = useState<boolean>(false);

  const formattedParams = (originParams: any) => {
    const params = {...originParams, ...extraFilterParams};
    if (params.created_at) {
      [params.start_time, params.end_time] = params.created_at;
      delete params.created_at;
    }

    if (params.relation_create_at) {
      [params.connection_create_start, params.connection_create_end] = params.relation_create_at;
      delete params.relation_create_at;
    }

    if (params.add_way) {
      params.channel_type = params.add_way;
      delete params.add_way;
    }

    return params;
  };

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

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

  const columns: ProColumns<CustomerItem>[] = [
    {
      fixed: 'left',
      title: '客户名',
      dataIndex: 'name',
      valueType: 'text',
      render: (dom, item) => {
        return (
          <div className={'customer-info-field'}>
            <a key='detail' onClick={() => {
              history.push(`/staff-admin/customer-management/customer/detail?ext_customer_id=${item.ext_customer_id}`);
            }}>
              <img
                src={item.avatar}
                className={'icon'}
                alt={item.name}
              />
            </a>
            <div className={'text-group'}>
              <p className={'text'}>
                {item.name}
              </p>
              {item.corp_name && (
                <p className={'text'} style={{color: '#eda150'}}>@{item.corp_name}</p>
              )}
              {item.type === 1 && (
                <p className={'text'} style={{color: '#5ec75d'}}>@微信</p>
              )}
            </div>
          </div>
        );
      },
    },
    {
      title: '添加人',
      dataIndex: 'ext_staff_ids',
      valueType: 'text',
      width: 200,
      renderFormItem: () => {
        return (
          <StaffTreeSelect options={allStaffs} maxTagCount={4}/>
        );
      },
      render: (dom, item) => {
        const staffs: StaffItem[] = [];
        item?.staff_relations?.forEach((staff_relation) => {
          // @ts-ignore
          const staff = staffMap[staff_relation.ext_staff_id];
          if (staff) {
            staffs.push(staff);
          }
        });
        return (
          <CollapsedStaffs limit={2} staffs={staffs}/>
        );
      },
    },
    {
      title: '标签',
      dataIndex: 'ext_tag_ids',
      valueType: 'text',
      hideInSearch: false,
      renderFormItem: () => {
        return (
          <CustomerTagSelect isEditable={false} allTagGroups={allTagGroups} maxTagCount={6}/>
        );
      },
      render: (dom, item) => {
        const tags: any[] = [];
        item.staff_relations?.forEach((relation) => {
          if (relation.ext_staff_id === localStorage.getItem('extStaffAdminID')) {
            relation.customer_staff_tags?.forEach((tag) => {
              tags.push(tag);
            });
          }
        });
        return <CollapsedTags limit={6} tags={tags}/>;
      },
    },

    {
      title: '添加时间',
      dataIndex: 'created_at',
      valueType: 'dateRange',
      sorter: true,
      filtered: true,
      render: (dom, item) => {
        if (item.staff_relations && item.staff_relations.length > 0) {
          const staff_relation = item.staff_relations[0];
          return (
            <div className={styles.staffTag}
                 dangerouslySetInnerHTML={{
                   __html: moment(staff_relation.createtime)
                     .format('YYYY-MM-DD HH:mm')
                     .split(' ')
                     .join('<br />'),
                 }}
            />
          );
        }
        return <></>;
      },

    },

    {
      title: '更新时间',
      dataIndex: ' updated_at',
      valueType: 'dateRange',
      // sorter: true,
      filtered: true,
      hideInSearch: true,
      render: (dom, item) => {
        return (
          <div
            dangerouslySetInnerHTML={{
              __html: moment(item.updated_at)
                .format('YYYY-MM-DD HH:mm')
                .split(' ')
                .join('<br />'),
            }}
          />
        );
      },
    },

    {
      title: '添加渠道',
      dataIndex: 'add_way',
      valueType: 'select',
      valueEnum: addWayEnums,
      // width: 220,
      render: (dom, item) => {
        return <span>{item.staff_relations?.map((para) => {
          return (`${addWayEnums[para.add_way || 0]}\n`);
        })}</span>;
      },
    },

    {
      title: '性别',
      dataIndex: 'gender',
      valueType: 'select',
      hideInTable: true,
      valueEnum: {
        1: '男',
        2: '女',
        3: '未知',
        0: '不限',
      },
    },

    {
      title: '账号类型',
      dataIndex: 'type',
      valueType: 'select',
      hideInTable: true,
      valueEnum: {
        1: '微信',
        2: '企业微信',
        0: '不限',
      },
    },

    {
      title: '流失状态',
      dataIndex: 'out_flow_status',
      valueType: 'select',
      hideInTable: true,
      hideInSearch: false,
      tooltip: '员工授权后的流失客户',
      valueEnum: {
        1: '已流失',
        2: '未流失',
      },
    },

    {
      title: '操作',
      width: 120,
      valueType: 'option',
      render: (dom, item) => [
        <a key='detail' onClick={() => {
          history.push(`/staff-admin/customer-management/customer/detail?ext_customer_id=${item.ext_customer_id}`);
        }}
        >详情</a>,
      ],
    },
  ];

  return (
    <PageContainer
      fixedHeader
      header={{
        title: '客户管理',
      }}
      extra={[
        <Button
          key={'export'}
          type='dashed'
          loading={exportLoading}
          icon={<CloudDownloadOutlined style={{fontSize: 16, verticalAlign: '-3px'}}/>}
          onClick={async () => {
            setExportLoading(true);
            try {
              const content = await ExportCustomer(
                formattedParams(queryFormRef.current?.getFieldsValue()),
              );
              const blob = new Blob([content], {
                type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
              });
              FileSaver.saveAs(blob, `客户数据列表.xlsx`);
            } catch (e) {
              console.log(e);
              message.error('导出失败');
            }
            setExportLoading(false);
          }}
        >
          导出Excel
        </Button>,


        <Button
          key={'sync'}
          type='dashed'
          icon={<SyncOutlined style={{fontSize: 16, verticalAlign: '-3px'}}/>}
          loading={syncLoading}
          onClick={async () => {
            setSyncLoading(true);
            const res: CommonResp = await Sync();
            if (res.code === 0) {
              setSyncLoading(false);
              message.success('同步成功');
            } else {
              setSyncLoading(false);
              message.error(res.message);
            }
          }}
        >
          同步数据
        </Button>,
      ]}
    >

      <ProTable
        rowSelection={{
          onChange: (__, items) => {
            setSelectedItems(items);
          },
        }}
        formRef={queryFormRef}
        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}
        params={{}}
        request={async (originParams: any, sort, filter) => {
          const res = ProTableRequestAdapter(
            formattedParams(originParams),
            sort,
            filter,
            QueryCustomer,
          );
          console.log(await res);
          return await res;
        }}
        dateFormatter='string'
      />

      {selectedItems?.length > 0 && (
        // 底部选中条目菜单栏
        <FooterToolbar>
          <span>
            已选择 <a style={{fontWeight: 600}}>{selectedItems.length}</a> 项 &nbsp;&nbsp;
          </span>
          <Divider type='vertical'/>
          <Button
            icon={<TagOutlined/>}
            type={'dashed'}
            onClick={() => {
              setBatchTagModalVisible(true);
            }}
          >
            批量打标签
          </Button>

        </FooterToolbar>
      )}

      <CustomerTagSelectionModal
        width={'630px'}
        visible={batchTagModalVisible}
        setVisible={setBatchTagModalVisible}
        onFinish={async (selectedTags) => {
          const selectedExtTagIDs = selectedTags.map((selectedTag) => selectedTag.ext_id);
          const selectedExtCustomerIDs = selectedItems.map((customer) => customer.ext_customer_id);
          await HandleRequest({
            add_ext_tag_ids: selectedExtTagIDs,
            ext_customer_ids: selectedExtCustomerIDs,
          }, UpdateCustomerTags, () => {
            // @ts-ignore
            actionRef?.current?.reloadAndRest();
            setSelectedItems([]);
          });
        }}
        allTagGroups={allTagGroups}
        isEditable={true}
        withLogicalCondition={false}
      />

    </PageContainer>
  );
}
Example #20
Source File: index.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
AutoReply: React.FC<AutoReplyProps> = (props) => {
  const {welcomeMsg, setWelcomeMsg, isFetchDone} = props;
  const [modalVisible, setModalVisible] = useState(false);
  const [attachments, setAttachments] = useState<Attachment[]>([]);
  const [currentIndex, setCurrentIndex] = useState<number>(0);
  const [currentMode, setCurrentMode] = useState<MsgType>('image');
  const [linkFetching, setLinkFetching] = useState(false);
  const [content, setContent] = useState('');
  const contentRef = useRef<React.RefObject<HTMLElement>>();
  const imageModalFormRef = useRef<FormInstance>();
  const linkModalFormRef = useRef<FormInstance>();
  const miniAppModalFormRef = useRef<FormInstance>();

  const UploadFileFn = async (req: UploadRequestOption, ref: MutableRefObject<any | undefined>, inputName: string) => {
    const file = req.file as File;
    if (!file.name) {
      message.error('非法参数');
      return;
    }

    const hide = message.loading('上传中');
    try {
      const res = await GetSignedURL(file.name)
      const data = res.data as GetSignedURLResult
      if (res.code === 0) {
        const uploadRes = (await fetch(data.upload_url, {
          method: 'PUT',
          body: file
        }));
        hide();
        if (uploadRes.ok && ref) {
          ref.current?.setFieldsValue({[inputName]: data.download_url});
          return;
        }

        message.error('上传图片失败');

        return;
      }

      hide();
      message.error('获取上传地址失败');
      return;

    } catch (e) {
      message.error('上传图片失败');
      console.log(e);
    }
  };

  useEffect(() => {
    const formData = itemDataToFormData(welcomeMsg);
    setAttachments(formData.attachments || []);
    setContent(formData.text || '');
  }, [isFetchDone]);

  useEffect(() => {
    setWelcomeMsg({
      text: content || '',
      attachments: attachments || [],
    });
  }, [content, attachments]);

  return (
    <>
      <div className={styles.replyEditor}>
        <div className={'preview-container'}>
          <div className={styles.replyEditorPreview}>
            <img src={phoneImage} className='bg'/>
            <div className='content'>
              <ul className='reply-list'>
                {content && (
                  <li><img
                    src={avatarDefault}/>
                    <div className='msg text' dangerouslySetInnerHTML={{__html: content}}/>
                  </li>
                )}
                {attachments && attachments.length > 0 && (
                  attachments.map((attachment) => {
                    if (attachment.msgtype === 'image') {
                      return (
                        <li key={attachment.id}>
                          <img src={avatarDefault}/>
                          <div className={`msg image`}>
                            <img src={attachment.image?.pic_url}/>
                          </div>
                        </li>
                      );
                    }

                    if (attachment.msgtype === 'link') {
                      return (
                        <li key={attachment.id}>
                          <img src={avatarDefault}/>
                          <div className='msg link'><p className='title'>{attachment.link?.title}</p>
                            <div className='link-inner'><p
                              className='desc'>{attachment.link?.desc}</p>
                              <img src={attachment.link?.picurl}/>
                            </div>
                          </div>
                        </li>
                      );
                    }

                    if (attachment.msgtype === 'miniprogram') {
                      return (
                        <li key={attachment.id}>
                          <img src={avatarDefault}/>
                          <div className='msg miniprogram'>
                            <p className='m-title'>
                              <IconFont
                                type={'icon-weixin-mini-app'}
                                style={{marginRight: 4, fontSize: 14}}
                              />
                              {attachment.miniprogram?.title}
                            </p>
                            <img src={attachment.miniprogram?.pic_media_id}/>
                            <p className='l-title'>
                              <IconFont type={'icon-weixin-mini-app'} style={{marginRight: 4}}/>
                              小程序
                            </p>
                          </div>
                        </li>
                      );
                    }

                    return '';
                  })
                )}
              </ul>
            </div>
          </div>
        </div>

        <div className='text-area-container'>
          <div className={styles.msgTextareaContainer} style={{border: 'none'}}>
            {props.enableQuickInsert && (
              <div className='insert-btn '>
                    <span
                      className='clickable no-select'
                      onClick={() => {
                        setContent(`${content}[客户昵称]`);
                      }}
                    >[插入客户昵称]</span>
              </div>
            )}
            <div className='textarea-container '>
              <ContentEditable
                // @ts-ignore
                innerRef={contentRef}
                onKeyDown={(event) => {
                  if (event.key === 'Enter') {
                    document.execCommand('insertLineBreak');
                    event.preventDefault();
                  }
                }}
                className={'textarea'}
                html={content}
                onChange={(e) => {
                  setContent(e.target.value);
                }}/>
              <div className='flex-row align-side'>
                <p className='text-cnt'>{content.length}/600</p>
              </div>
            </div>
          </div>
        </div>
        <div className='option-area-container'>
          {attachments && attachments.length > 0 && (
            <ReactSortable handle={'.draggable-button'} tag='ul' className={'select-msg-options'} list={attachments} setList={setAttachments}>
              {attachments.map((attachment, index) => (
                <li key={attachment.id} className='flex-row'>
                      <span>
                        <MinusCircleOutlined
                          onClick={() => {
                            const items = [...attachments];
                            items.splice(index, 1);
                            setAttachments(items);
                          }}
                        />
                        【{msgTypes[attachment.msgtype]}】:
                        <span
                          className='col-1'>{attachment?.name}</span>
                      </span>
                  <span className='d-action-container'>
                      <EditOutlined
                        onClick={() => {
                          setCurrentMode(attachment.msgtype);
                          imageModalFormRef.current?.setFieldsValue(attachment.image);
                          linkModalFormRef.current?.setFieldsValue(attachment.link);
                          miniAppModalFormRef.current?.setFieldsValue(attachment.miniprogram);
                          setCurrentIndex(index);
                          setModalVisible(true);
                        }}
                      />
                      <DragOutlined
                        className={'draggable-button'}
                        style={{cursor: 'grabbing'}}
                      />
                    </span>
                </li>
              ))}
            </ReactSortable>
          )}
          <div className='option-container'>
            <Dropdown
              placement='topLeft'
              trigger={['click']}
              overlay={(
                <Menu style={{minWidth: 120}}>
                  <Menu.Item
                    key={'image'}
                    icon={<FileImageOutlined/>}
                    onClick={() => {
                      setCurrentMode('image');
                      setCurrentIndex(attachments.length);
                      imageModalFormRef.current?.resetFields();
                      setModalVisible(true);
                    }}
                  >
                    图片
                  </Menu.Item>
                  <Menu.Item
                    key={'link'}
                    icon={<LinkOutlined/>}
                    onClick={() => {
                      setCurrentMode('link');
                      setCurrentIndex(attachments.length);
                      setModalVisible(true);
                    }}
                  >
                    链接
                  </Menu.Item>
                  <Menu.Item
                    key={'miniApp'}
                    icon={<IconFont type={'icon-weixin-mini-app'}/>}
                    onClick={() => {
                      setCurrentMode('miniprogram');
                      setCurrentIndex(attachments.length);
                      setModalVisible(true);
                    }}
                  >
                    小程序
                  </Menu.Item>
                </Menu>
              )}
            >
              <a className='ant-dropdown-link' onClick={e => e.preventDefault()}>
                <PlusCircleOutlined/> 添加附件
              </a>
            </Dropdown>
          </div>
        </div>
      </div>

      <ModalForm
        formRef={imageModalFormRef}
        className={'dialog from-item-label-100w'}
        layout={'horizontal'}
        width={'560px'}
        visible={currentMode === 'image' && modalVisible}
        onVisibleChange={setModalVisible}
        onFinish={async (params: { title: string, pic_url: string, msgtype: MsgType }) => {
          attachments[currentIndex] = {
            id: new Date().getTime().toString(),
            msgtype: params.msgtype,
            name: params.title,
            image: {...params},
          };
          setAttachments(attachments);
          return true;
        }}
      >
        <h2 className='dialog-title'> 添加图片附件 </h2>
        <ProForm.Item initialValue={'image'} name={'msgtype'} noStyle={true}>
          <input type={'hidden'}/>
        </ProForm.Item>
        <ProFormText
          name='title'
          label='图片名称'
          placeholder={'请输入图片名称'}
          width='md'
          rules={[
            {
              required: true,
              message: '请输入图片名称!',
            },
          ]}
        />
        <Form.Item
          label='上传图片'
          name='pic_url'
          rules={[
            {
              required: true,
              message: '请上传图片!',
            },
          ]}
        >
          <ImageUploader
            customRequest={async (req) => {
              await UploadFileFn(req, imageModalFormRef, 'pic_url')
            }}
          />
        </Form.Item>
      </ModalForm>


      <ModalForm
        formRef={linkModalFormRef}
        className={'dialog from-item-label-100w'}
        layout={'horizontal'}
        width={'560px'}
        visible={currentMode === 'link' && modalVisible}
        onVisibleChange={setModalVisible}
        onFinish={async (params) => {
          attachments[currentIndex] = {
            id: new Date().getTime().toString(),
            msgtype: params.msgtype,
            name: params.title,
            // @ts-ignore
            link: {...params},
          };
          setAttachments(attachments);
          return true;
        }}
      >
        <Spin spinning={linkFetching}>
          <h2 className='dialog-title'> 添加链接附件 </h2>
          <ProForm.Item initialValue={'link'} name={'msgtype'} noStyle={true}>
            <input type={'hidden'}/>
          </ProForm.Item>
          <ProFormText
            name='url'
            label='链接地址'
            width='md'
            fieldProps={{
              disabled: linkFetching,
              addonAfter: (
                <Tooltip title="点击抓取远程链接,自动填充标题,描述,图片">
                  <div
                    onClick={async () => {
                      setLinkFetching(true);
                      const res = await ParseURL(linkModalFormRef.current?.getFieldValue('url'))
                      setLinkFetching(false);
                      if (res.code !== 0) {
                        message.error(res.message);
                      } else {
                        message.success('解析链接成功');
                        linkModalFormRef?.current?.setFieldsValue({
                          customer_link_enable: 1,
                          title: res.data.title,
                          desc: res.data.desc,
                          picurl: res.data.img_url,
                        })
                      }
                    }}
                    style={{
                      cursor: "pointer",
                      width: 32,
                      height: 30,
                      display: 'flex',
                      alignItems: 'center',
                      justifyContent: 'center'
                    }}>
                    <SyncOutlined/>
                  </div>
                </Tooltip>
              )
            }}
            rules={[
              {
                required: true,
                message: '请输入链接地址',
              },
              {
                type: 'url',
                message: '请填写正确的的URL,必须是http或https开头',
              },
            ]}
          />
          <ProFormSwitch
            label={'高级设置'}
            checkedChildren='开启'
            unCheckedChildren='关闭'
            name='customer_link_enable'
            tooltip={'开启后可以自定义链接所有信息'}
          />
          <ProFormDependency name={['customer_link_enable']}>
            {({customer_link_enable}) => {
              if (customer_link_enable) {
                return (
                  <>
                    <ProFormText
                      name='title'
                      label='链接标题'
                      width='md'
                      rules={[
                        {
                          required: true,
                          message: '请输入链接标题',
                        },
                      ]}
                    />
                    <ProFormTextArea
                      name='desc'
                      label='链接描述'
                      width='md'
                    />
                    <Form.Item
                      label='链接封面'
                      name='picurl'
                      rules={[
                        {
                          required: true,
                          message: '请上传链接图片!',
                        },
                      ]}
                    >
                      <ImageUploader
                        customRequest={async (req) => {
                          await UploadFileFn(req, linkModalFormRef, 'picurl')
                        }}
                      />
                    </Form.Item>
                  </>
                );
              }
              return <></>;
            }}
          </ProFormDependency>
        </Spin>
      </ModalForm>


      <ModalForm
        formRef={miniAppModalFormRef}
        className={'dialog from-item-label-100w'}
        layout={'horizontal'}
        width={'560px'}
        labelCol={{
          md: 6,
        }}
        visible={currentMode === 'miniprogram' && modalVisible}
        onVisibleChange={setModalVisible}
        onFinish={async (params) => {
          attachments[currentIndex] = {
            id: new Date().getTime().toString(),
            msgtype: params.msgtype,
            name: params.title,
            // @ts-ignore
            miniprogram: {...params},
          };
          setAttachments(attachments);
          return true;
        }}
      >
        <h2 className='dialog-title'> 添加小程序附件 </h2>

        <Alert
          showIcon={true}
          type='info'
          message={
            '请填写企业微信后台绑定的小程序id和路径,否则会造成发送失败'
          }
          style={{marginBottom: 20}}
        />

        <ProForm.Item initialValue={'miniprogram'} name={'msgtype'} noStyle={true}>
          <input type={'hidden'}/>
        </ProForm.Item>

        <ProFormText
          name='title'
          label='小程序标题'
          width='md'
          rules={[
            {
              required: true,
              message: '请输入链接标题',
            },
          ]}
        />

        <ProFormText
          // 帮助指引
          name='app_id'
          label='小程序AppID'
          width='md'
          rules={[
            {
              required: true,
              message: '请输入小程序AppID',
            },
          ]}
        />

        <ProFormText
          name='page'
          label='小程序路径'
          width='md'
          rules={[
            {
              required: true,
              message: '请输入小程序路径',
            },
          ]}
        />

        <Form.Item
          label='小程序封面'
          name='pic_media_id'
          rules={[
            {
              required: true,
              message: '请小程序封面!',
            },
          ]}
        >
          <ImageUploader
            customRequest={async (req) => {
              await UploadFileFn(req, miniAppModalFormRef, 'pic_media_id')
            }}
          />
        </Form.Item>

      </ModalForm>

    </>
  );
}
Example #21
Source File: index.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
function index(props: IProps) {
  const { dashboardId, id, time, refreshFlag, step, type, variableConfig, values, isPreview, onCloneClick, onShareClick, onEditClick, onDeleteClick } = props;
  const ref = useRef<HTMLDivElement>(null);
  const [inViewPort] = useInViewport(ref);
  const { series, loading } = usePrometheus({
    id,
    dashboardId,
    time,
    refreshFlag,
    step,
    targets: values.targets,
    variableConfig,
    inViewPort: isPreview || inViewPort,
  });
  const subProps = {
    values,
    series,
  };
  const tipsVisible = values.description || !_.isEmpty(values.links);
  if (_.isEmpty(values)) return null;
  const RendererCptMap = {
    timeseries: () => <Timeseries {...subProps} />,
    stat: () => <Stat {...subProps} />,
    table: () => <Table {...subProps} />,
    pie: () => <Pie {...subProps} />,
  };

  return (
    <div className='renderer-container' ref={ref}>
      <div className='renderer-header graph-header dashboards-panels-item-drag-handle'>
        {tipsVisible ? (
          <Tooltip
            placement='rightTop'
            overlayInnerStyle={{
              width: 300,
            }}
            title={
              <div>
                <Markdown content={values.description} />
                <div>
                  {_.map(values.links, (link) => {
                    return (
                      <div style={{ marginTop: 8 }}>
                        <a href={link.url} target={link.targetBlank ? '_blank' : '_self'}>
                          {link.title}
                        </a>
                      </div>
                    );
                  })}
                </div>
              </div>
            }
          >
            <div className='renderer-header-desc'>
              <span className='renderer-header-info-corner-inner' />
              {values.description ? <InfoOutlined /> : <LinkOutlined />}
            </div>
          </Tooltip>
        ) : null}
        <div className='renderer-header-content'>
          {!isPreview ? (
            <Dropdown
              trigger={['click']}
              placement='bottomCenter'
              overlayStyle={{
                minWidth: '100px',
              }}
              overlay={
                <Menu>
                  {!isPreview ? (
                    <>
                      <Menu.Item onClick={onEditClick} key='0'>
                        <SettingOutlined style={{ marginRight: 8 }} />
                        编辑
                      </Menu.Item>
                      <Menu.Item onClick={onCloneClick} key='1'>
                        <CopyOutlined style={{ marginRight: 8 }} />
                        克隆
                      </Menu.Item>
                      <Menu.Item onClick={onShareClick} key='2'>
                        <ShareAltOutlined style={{ marginRight: 8 }} />
                        分享
                      </Menu.Item>
                      <Menu.Item onClick={onDeleteClick} key='3'>
                        <DeleteOutlined style={{ marginRight: 8 }} />
                        删除
                      </Menu.Item>
                    </>
                  ) : null}
                </Menu>
              }
            >
              <div className='renderer-header-title'>
                {values.name}
                <DownOutlined className='renderer-header-arrow' />
              </div>
            </Dropdown>
          ) : (
            <div className='renderer-header-title'>{values.name}</div>
          )}
        </div>
        <div className='renderer-header-loading'>{loading && <SyncOutlined spin />}</div>
      </div>
      <div className='renderer-body' style={{ height: `calc(100% - 36px)` }}>
        {RendererCptMap[type] ? RendererCptMap[type]() : `无效的图表类型 ${type}`}
      </div>
    </div>
  );
}
Example #22
Source File: index.tsx    From ql with MIT License 4 votes vote down vote up
Config = () => {
  const columns = [
    {
      title: '序号',
      align: 'center' as const,
      render: (text: string, record: any, index: number) => {
        return <span style={{ cursor: 'text' }}>{index + 1} </span>;
      },
    },
    {
      title: '昵称',
      dataIndex: 'nickname',
      key: 'nickname',
      align: 'center' as const,
      width: '15%',
      render: (text: string, record: any, index: number) => {
        const match = record.value.match(/pt_pin=([^; ]+)(?=;?)/);
        const val = (match && match[1]) || '未匹配用户名';
        return (
          <span style={{ cursor: 'text' }}>{record.nickname || val} </span>
        );
      },
    },
    {
      title: '值',
      dataIndex: 'value',
      key: 'value',
      align: 'center' as const,
      width: '50%',
      render: (text: string, record: any) => {
        return (
          <span
            style={{
              textAlign: 'left',
              display: 'inline-block',
              wordBreak: 'break-all',
              cursor: 'text',
            }}
          >
            {text}
          </span>
        );
      },
    },
    {
      title: '状态',
      key: 'status',
      dataIndex: 'status',
      align: 'center' as const,
      width: '15%',
      render: (text: string, record: any, index: number) => {
        return (
          <Space size="middle" style={{ cursor: 'text' }}>
            <Tag
              color={StatusColor[record.status] || StatusColor[3]}
              style={{ marginRight: 0 }}
            >
              {Status[record.status]}
            </Tag>
            {record.status !== Status.已禁用 && (
              <Tooltip title="刷新">
                <a onClick={() => refreshStatus(record, index)}>
                  <SyncOutlined />
                </a>
              </Tooltip>
            )}
          </Space>
        );
      },
    },
    {
      title: '操作',
      key: 'action',
      align: 'center' as const,
      render: (text: string, record: any, index: number) => (
        <Space size="middle">
          <Tooltip title="编辑">
            <a onClick={() => editCookie(record, index)}>
              <EditOutlined />
            </a>
          </Tooltip>
          <Tooltip title={record.status === Status.已禁用 ? '启用' : '禁用'}>
            <a onClick={() => enabledOrDisabledCookie(record, index)}>
              {record.status === Status.已禁用 ? (
                <CheckCircleOutlined />
              ) : (
                <StopOutlined />
              )}
            </a>
          </Tooltip>
          <Tooltip title="删除">
            <a onClick={() => deleteCookie(record, index)}>
              <DeleteOutlined />
            </a>
          </Tooltip>
        </Space>
      ),
    },
  ];
  const [width, setWidth] = useState('100%');
  const [marginLeft, setMarginLeft] = useState(0);
  const [marginTop, setMarginTop] = useState(-72);
  const [value, setValue] = useState<any[]>([]);
  const [loading, setLoading] = useState(true);
  const [isModalVisible, setIsModalVisible] = useState(false);
  const [editedCookie, setEditedCookie] = useState();
  const [selectedRowIds, setSelectedRowIds] = useState<string[]>([]);

  const getCookies = () => {
    setLoading(true);
    request
      .get(`${config.apiPrefix}cookies`)
      .then((data: any) => {
        setValue(data.data);
      })
      .finally(() => setLoading(false));
  };

  const refreshStatus = (record: any, index: number) => {
    request
      .get(`${config.apiPrefix}cookies/${record._id}/refresh`)
      .then(async (data: any) => {
        if (data.data && data.data.value) {
          (value as any).splice(index, 1, data.data);
          setValue([...(value as any)] as any);
        } else {
          message.error('更新状态失败');
        }
      });
  };

  const enabledOrDisabledCookie = (record: any, index: number) => {
    Modal.confirm({
      title: `确认${record.status === Status.已禁用 ? '启用' : '禁用'}`,
      content: (
        <>
          确认{record.status === Status.已禁用 ? '启用' : '禁用'}
          Cookie{' '}
          <Text style={{ wordBreak: 'break-all' }} type="warning">
            {record.value}
          </Text>{' '}
          吗
        </>
      ),
      onOk() {
        request
          .put(
            `${config.apiPrefix}cookies/${
              record.status === Status.已禁用 ? 'enable' : 'disable'
            }`,
            {
              data: [record._id],
            },
          )
          .then((data: any) => {
            if (data.code === 200) {
              message.success(
                `${record.status === Status.已禁用 ? '启用' : '禁用'}成功`,
              );
              const newStatus =
                record.status === Status.已禁用 ? Status.未获取 : Status.已禁用;
              const result = [...value];
              result.splice(index, 1, {
                ...record,
                status: newStatus,
              });
              setValue(result);
            } else {
              message.error(data);
            }
          });
      },
      onCancel() {
        console.log('Cancel');
      },
    });
  };

  const addCookie = () => {
    setEditedCookie(null as any);
    setIsModalVisible(true);
  };

  const editCookie = (record: any, index: number) => {
    setEditedCookie(record);
    setIsModalVisible(true);
  };

  const deleteCookie = (record: any, index: number) => {
    Modal.confirm({
      title: '确认删除',
      content: (
        <>
          确认删除Cookie{' '}
          <Text style={{ wordBreak: 'break-all' }} type="warning">
            {record.value}
          </Text>{' '}
          吗
        </>
      ),
      onOk() {
        request
          .delete(`${config.apiPrefix}cookies`, { data: [record._id] })
          .then((data: any) => {
            if (data.code === 200) {
              message.success('删除成功');
              const result = [...value];
              result.splice(index, 1);
              setValue(result);
            } else {
              message.error(data);
            }
          });
      },
      onCancel() {
        console.log('Cancel');
      },
    });
  };

  const handleCancel = (cookies?: any[]) => {
    setIsModalVisible(false);
    if (cookies && cookies.length > 0) {
      handleCookies(cookies);
    }
  };

  const handleCookies = (cookies: any[]) => {
    const result = [...value];
    for (let i = 0; i < cookies.length; i++) {
      const cookie = cookies[i];
      const index = value.findIndex((x) => x._id === cookie._id);
      if (index === -1) {
        result.push(cookie);
      } else {
        result.splice(index, 1, {
          ...cookie,
        });
      }
    }
    setValue(result);
  };

  const components = {
    body: {
      row: DragableBodyRow,
    },
  };

  const moveRow = useCallback(
    (dragIndex, hoverIndex) => {
      if (dragIndex === hoverIndex) {
        return;
      }
      const dragRow = value[dragIndex];
      const newData = [...value];
      newData.splice(dragIndex, 1);
      newData.splice(hoverIndex, 0, dragRow);
      setValue([...newData]);
      request
        .put(`${config.apiPrefix}cookies/${dragRow._id}/move`, {
          data: { fromIndex: dragIndex, toIndex: hoverIndex },
        })
        .then((data: any) => {
          if (data.code !== 200) {
            message.error(data);
          }
        });
    },
    [value],
  );

  const onSelectChange = (selectedIds: any[]) => {
    setSelectedRowIds(selectedIds);
  };

  const rowSelection = {
    selectedRowIds,
    onChange: onSelectChange,
  };

  const delCookies = () => {
    Modal.confirm({
      title: '确认删除',
      content: <>确认删除选中的Cookie吗</>,
      onOk() {
        request
          .delete(`${config.apiPrefix}cookies`, { data: selectedRowIds })
          .then((data: any) => {
            if (data.code === 200) {
              message.success('批量删除成功');
              setSelectedRowIds([]);
              getCookies();
            } else {
              message.error(data);
            }
          });
      },
      onCancel() {
        console.log('Cancel');
      },
    });
  };

  const operateCookies = (operationStatus: number) => {
    Modal.confirm({
      title: `确认${OperationName[operationStatus]}`,
      content: <>确认{OperationName[operationStatus]}选中的Cookie吗</>,
      onOk() {
        request
          .put(`${config.apiPrefix}cookies/${OperationPath[operationStatus]}`, {
            data: selectedRowIds,
          })
          .then((data: any) => {
            if (data.code === 200) {
              getCookies();
            } else {
              message.error(data);
            }
          });
      },
      onCancel() {
        console.log('Cancel');
      },
    });
  };

  useEffect(() => {
    if (document.body.clientWidth < 768) {
      setWidth('auto');
      setMarginLeft(0);
      setMarginTop(0);
    } else {
      setWidth('100%');
      setMarginLeft(0);
      setMarginTop(-72);
    }
    getCookies();
  }, []);

  return (
    <PageContainer
      className="session-wrapper"
      title="Session管理"
      extra={[
        <Button key="2" type="primary" onClick={() => addCookie()}>
          添加Cookie
        </Button>,
      ]}
      header={{
        style: {
          padding: '4px 16px 4px 15px',
          position: 'sticky',
          top: 0,
          left: 0,
          zIndex: 20,
          marginTop,
          width,
          marginLeft,
        },
      }}
    >
      {selectedRowIds.length > 0 && (
        <div style={{ marginBottom: 16 }}>
          <Button
            type="primary"
            style={{ marginBottom: 5 }}
            onClick={delCookies}
          >
            批量删除
          </Button>
          <Button
            type="primary"
            onClick={() => operateCookies(0)}
            style={{ marginLeft: 8, marginBottom: 5 }}
          >
            批量启用
          </Button>
          <Button
            type="primary"
            onClick={() => operateCookies(1)}
            style={{ marginLeft: 8, marginRight: 8 }}
          >
            批量禁用
          </Button>
          <span style={{ marginLeft: 8 }}>
            已选择
            <a>{selectedRowIds?.length}</a>项
          </span>
        </div>
      )}
      <DndProvider backend={HTML5Backend}>
        <Table
          columns={columns}
          rowSelection={rowSelection}
          pagination={false}
          dataSource={value}
          rowKey="_id"
          size="middle"
          scroll={{ x: 768 }}
          components={components}
          loading={loading}
          onRow={(record, index) => {
            return {
              index,
              moveRow,
            } as any;
          }}
        />
      </DndProvider>
      <CookieModal
        visible={isModalVisible}
        handleCancel={handleCancel}
        cookie={editedCookie}
      />
    </PageContainer>
  );
}
Example #23
Source File: Graph.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
export default function Graph(props: IProps) {
  const { metric, match, range, step, onClose } = props;
  const newGroups = _.map(
    _.filter(match.dimensionLabels, (item) => !_.isEmpty(item.value)),
    'label',
  );
  const [refreshFlag, setRefreshFlag] = useState(_.uniqueId('refreshFlag_'));
  const [calcFunc, setCalcFunc] = useState('');
  const [comparison, setComparison] = useState<string[]>([]);
  const [aggrFunc, setAggrFunc] = useState('avg');
  const [aggrGroups, setAggrGroups] = useState<string[]>(newGroups);
  const [labels, setLabels] = useState<string[]>([]);
  const [series, setSeries] = useState<any[]>([]);
  const [highLevelConfig, setHighLevelConfig] = useState({
    shared: true,
    sharedSortDirection: 'desc',
    legend: true,
    util: 'none',
    colorRange: colors[0].value,
    reverseColorOrder: false,
    colorDomainAuto: true,
    colorDomain: [],
    chartheight: 300,
  });
  const [chartType, setChartType] = useState('line');
  const [reduceFunc, setReduceFunc] = useState('last');
  const lineGraphProps = {
    custom: {
      drawStyle: 'lines',
      fillOpacity: 0,
      stack: 'hidden',
      lineInterpolation: 'smooth',
    },
    options: {
      legend: {
        displayMode: highLevelConfig.legend ? 'list' : 'hidden',
      },
      tooltip: {
        mode: highLevelConfig.shared ? 'all' : 'single',
        sort: highLevelConfig.sharedSortDirection,
      },
      standardOptions: {
        util: highLevelConfig.util,
      },
    },
  };
  const hexbinGraphProps = {
    custom: {
      calc: reduceFunc,
      colorRange: highLevelConfig.colorRange,
      reverseColorOrder: highLevelConfig.reverseColorOrder,
      colorDomainAuto: highLevelConfig.colorDomainAuto,
      colorDomain: highLevelConfig.colorDomain,
    },
    options: {
      standardOptions: {
        util: highLevelConfig.util,
      },
    },
  };
  const graphStandardOptions = {
    line: <LineGraphStandardOptions highLevelConfig={highLevelConfig} setHighLevelConfig={setHighLevelConfig} />,
    hexbin: <HexbinGraphStandardOptions highLevelConfig={highLevelConfig} setHighLevelConfig={setHighLevelConfig} />,
  };

  useEffect(() => {
    setAggrGroups(newGroups);
  }, [JSON.stringify(newGroups)]);

  useEffect(() => {
    const matchStr = getMatchStr(match);
    getLabels(`${metric}${matchStr}`, range).then((res) => {
      setLabels(res);
    });
  }, [refreshFlag, JSON.stringify(match), JSON.stringify(range)]);

  useEffect(() => {
    getQueryRange({
      metric,
      match: getMatchStr(match),
      range,
      step,
      aggrFunc,
      aggrGroups,
      calcFunc,
      comparison,
    }).then((res) => {
      setSeries(res);
    });
  }, [refreshFlag, metric, JSON.stringify(match), JSON.stringify(range), step, calcFunc, comparison, aggrFunc, aggrGroups]);

  return (
    <Card
      size='small'
      style={{ marginBottom: 10 }}
      title={metric}
      className='n9e-metric-views-metrics-graph'
      extra={
        <Space>
          <Space size={0} style={{ marginRight: 10 }}>
            <LineChartOutlined
              className={classNames({
                'button-link-icon': true,
                active: chartType === 'line',
              })}
              onClick={() => {
                setChartType('line');
              }}
            />
            <Divider type='vertical' />
            <HexbinIcon
              className={classNames({
                'button-link-icon': true,
                active: chartType === 'hexbin',
              })}
              onClick={() => {
                setChartType('hexbin');
              }}
            />
          </Space>
          <Popover placement='left' content={graphStandardOptions[chartType]} trigger='click' autoAdjustOverflow={false} getPopupContainer={() => document.body}>
            <a>
              <SettingOutlined />
            </a>
          </Popover>
          <a>
            <SyncOutlined
              onClick={() => {
                setRefreshFlag(_.uniqueId('refreshFlag_'));
              }}
            />
          </a>
          <a>
            <ShareAltOutlined
              onClick={() => {
                const curCluster = localStorage.getItem('curCluster');
                const dataProps = {
                  type: 'timeseries',
                  version: '2.0.0',
                  name: metric,
                  step,
                  range,
                  ...lineGraphProps,
                  targets: _.map(
                    getExprs({
                      metric,
                      match: getMatchStr(match),
                      aggrFunc,
                      aggrGroups,
                      calcFunc,
                      comparison,
                    }),
                    (expr) => {
                      return {
                        expr,
                      };
                    },
                  ),
                };
                setTmpChartData([
                  {
                    configs: JSON.stringify({
                      curCluster,
                      dataProps,
                    }),
                  },
                ]).then((res) => {
                  const ids = res.dat;
                  window.open('/chart/' + ids);
                });
              }}
            />
          </a>
          <a>
            <CloseCircleOutlined onClick={onClose} />
          </a>
        </Space>
      }
    >
      <div>
        <Space>
          <div>
            计算函数:
            <Dropdown
              overlay={
                <Menu onClick={(e) => setCalcFunc(e.key === 'clear' ? '' : e.key)} selectedKeys={[calcFunc]}>
                  <Menu.Item key='rate_1m'>rate_1m</Menu.Item>
                  <Menu.Item key='rate_5m'>rate_5m</Menu.Item>
                  <Menu.Item key='increase_1m'>increase_1m</Menu.Item>
                  <Menu.Item key='increase_5m'>increase_5m</Menu.Item>
                  <Menu.Divider></Menu.Divider>
                  <Menu.Item key='clear'>clear</Menu.Item>
                </Menu>
              }
            >
              <a className='ant-dropdown-link' onClick={(e) => e.preventDefault()}>
                {calcFunc || '无'} <DownOutlined />
              </a>
            </Dropdown>
          </div>
          <div>
            环比:
            {comparison.map((ag) => (
              <Tag
                key={ag}
                closable
                onClose={() => {
                  setComparison(_.without(comparison, ag));
                }}
              >
                {ag}
              </Tag>
            ))}
            <Dropdown
              overlay={
                <Menu
                  onClick={(e) => {
                    if (comparison.indexOf(e.key) === -1) {
                      setComparison([...comparison, e.key]);
                    } else {
                      setComparison(_.without(comparison, e.key));
                    }
                  }}
                  selectedKeys={comparison}
                >
                  <Menu.Item key='1d'>1d</Menu.Item>
                  <Menu.Item key='7d'>7d</Menu.Item>
                </Menu>
              }
              overlayStyle={{ maxHeight: 400, overflow: 'auto' }}
            >
              <a className='ant-dropdown-link' onClick={(e) => e.preventDefault()}>
                <PlusCircleOutlined />
              </a>
            </Dropdown>
          </div>
          <div>
            聚合函数:
            <Dropdown
              overlay={
                <Menu onClick={(e) => setAggrFunc(e.key)} selectedKeys={[aggrFunc]}>
                  <Menu.Item key='sum'>sum</Menu.Item>
                  <Menu.Item key='avg'>avg</Menu.Item>
                  <Menu.Item key='max'>max</Menu.Item>
                  <Menu.Item key='min'>min</Menu.Item>
                </Menu>
              }
            >
              <a className='ant-dropdown-link' onClick={(e) => e.preventDefault()}>
                {aggrFunc} <DownOutlined />
              </a>
            </Dropdown>
          </div>
          {aggrFunc ? (
            <div className='graph-config-inner-item'>
              聚合维度:
              {aggrGroups.map((ag) => (
                <Tag
                  key={ag}
                  closable
                  onClose={() => {
                    setAggrGroups(_.without(aggrGroups, ag));
                  }}
                >
                  {ag}
                </Tag>
              ))}
              <Dropdown
                overlay={
                  <Menu
                    onClick={(e) => {
                      if (aggrGroups.indexOf(e.key) === -1) {
                        setAggrGroups([...aggrGroups, e.key]);
                      } else {
                        setAggrGroups(_.without(aggrGroups, e.key));
                      }
                    }}
                    selectedKeys={aggrGroups}
                  >
                    {_.map(
                      _.filter(labels, (n) => n !== '__name__'),
                      (ag) => (
                        <Menu.Item key={ag}>{ag}</Menu.Item>
                      ),
                    )}
                  </Menu>
                }
                overlayStyle={{ maxHeight: 400, overflow: 'auto' }}
              >
                <a className='ant-dropdown-link' onClick={(e) => e.preventDefault()}>
                  <PlusCircleOutlined />
                </a>
              </Dropdown>
            </div>
          ) : null}
          {chartType === 'hexbin' && (
            <div>
              取值计算:
              <Dropdown
                overlay={
                  <Menu onClick={(e) => setReduceFunc(e.key)} selectedKeys={[reduceFunc]}>
                    {_.map(calcsOptions, (val, key) => {
                      return <Menu.Item key={key}>{val.name}</Menu.Item>;
                    })}
                  </Menu>
                }
              >
                <a className='ant-dropdown-link' onClick={(e) => e.preventDefault()}>
                  {calcsOptions[reduceFunc]?.name} <DownOutlined />
                </a>
              </Dropdown>
            </div>
          )}
        </Space>
      </div>
      <div>
        {chartType === 'line' && <Timeseries inDashboard={false} values={lineGraphProps as any} series={series} />}
        {chartType === 'hexbin' && (
          <div style={{ padding: '20px 0 0 0', height: highLevelConfig.chartheight }}>
            <Hexbin values={hexbinGraphProps as any} series={series} />
          </div>
        )}
      </div>
    </Card>
  );
}
Example #24
Source File: index.tsx    From fe-v5 with Apache License 2.0 4 votes vote down vote up
function index(props: IProps) {
  const { dashboardId, id, time, refreshFlag, step, type, variableConfig, isPreview, onCloneClick, onShareClick, onEditClick, onDeleteClick } = props;
  const values = _.cloneDeep(props.values);
  const ref = useRef<HTMLDivElement>(null);
  const [inViewPort] = useInViewport(ref);
  const { series, loading } = usePrometheus({
    id,
    dashboardId,
    time,
    refreshFlag,
    step,
    targets: values.targets,
    variableConfig,
    inViewPort: isPreview || inViewPort,
  });
  const tipsVisible = values.description || !_.isEmpty(values.links);
  if (_.isEmpty(values)) return null;
  // TODO: 如果 hexbin 的 colorRange 为 string 时转成成 array
  if (typeof _.get(values, 'custom.colorRange') === 'string') {
    _.set(values, 'custom.colorRange', _.split(_.get(values, 'custom.colorRange'), ','));
  }
  const subProps = {
    values,
    series,
  };
  const RendererCptMap = {
    timeseries: () => <Timeseries {...subProps} />,
    stat: () => <Stat {...subProps} />,
    table: () => <Table {...subProps} />,
    pie: () => <Pie {...subProps} />,
    hexbin: () => <Hexbin {...subProps} />,
  };

  return (
    <div className='renderer-container' ref={ref}>
      <div className='renderer-header graph-header dashboards-panels-item-drag-handle'>
        {tipsVisible ? (
          <Tooltip
            placement='rightTop'
            overlayInnerStyle={{
              width: 300,
            }}
            title={
              <div>
                <Markdown content={values.description} />
                <div>
                  {_.map(values.links, (link, i) => {
                    return (
                      <div key={i} style={{ marginTop: 8 }}>
                        <a href={link.url} target={link.targetBlank ? '_blank' : '_self'}>
                          {link.title}
                        </a>
                      </div>
                    );
                  })}
                </div>
              </div>
            }
          >
            <div className='renderer-header-desc'>
              <span className='renderer-header-info-corner-inner' />
              {values.description ? <InfoOutlined /> : <LinkOutlined />}
            </div>
          </Tooltip>
        ) : null}
        <div className='renderer-header-content'>
          {!isPreview ? (
            <Dropdown
              trigger={['click']}
              placement='bottomCenter'
              overlayStyle={{
                minWidth: '100px',
              }}
              overlay={
                <Menu>
                  {!isPreview ? (
                    <>
                      <Menu.Item onClick={onEditClick} key='0'>
                        <SettingOutlined style={{ marginRight: 8 }} />
                        编辑
                      </Menu.Item>
                      <Menu.Item onClick={onCloneClick} key='1'>
                        <CopyOutlined style={{ marginRight: 8 }} />
                        克隆
                      </Menu.Item>
                      <Menu.Item onClick={onShareClick} key='2'>
                        <ShareAltOutlined style={{ marginRight: 8 }} />
                        分享
                      </Menu.Item>
                      <Menu.Item onClick={onDeleteClick} key='3'>
                        <DeleteOutlined style={{ marginRight: 8 }} />
                        删除
                      </Menu.Item>
                    </>
                  ) : null}
                </Menu>
              }
            >
              <div className='renderer-header-title'>
                {values.name}
                <DownOutlined className='renderer-header-arrow' />
              </div>
            </Dropdown>
          ) : (
            <div className='renderer-header-title'>{values.name}</div>
          )}
        </div>
        <div className='renderer-header-loading'>{loading && <SyncOutlined spin />}</div>
      </div>
      <div className='renderer-body' style={{ height: `calc(100% - 36px)` }}>
        {RendererCptMap[type] ? RendererCptMap[type]() : `无效的图表类型 ${type}`}
      </div>
    </div>
  );
}
Example #25
Source File: palette.tsx    From jmix-frontend with Apache License 2.0 4 votes vote down vote up
palette = () => (
  <Palette>
    <Category name="Text">
      <Component name="Formatted Message">
        <Variant>
          <FormattedMessage />
        </Variant>
      </Component>
      <Component name="Heading">
        <Variant name="h1">
          <Typography.Title></Typography.Title>
        </Variant>
        <Variant name="h2">
          <Typography.Title level={2}></Typography.Title>
        </Variant>
        <Variant name="h3">
          <Typography.Title level={3}></Typography.Title>
        </Variant>
        <Variant name="h4">
          <Typography.Title level={4}></Typography.Title>
        </Variant>
        <Variant name="h5">
          <Typography.Title level={5}></Typography.Title>
        </Variant>
      </Component>
      <Component name="Text">
        <Variant>
          <Typography.Text></Typography.Text>
        </Variant>
        <Variant name="Secondary">
          <Typography.Text type="secondary"></Typography.Text>
        </Variant>
        <Variant name="Success">
          <Typography.Text type="success"></Typography.Text>
        </Variant>
        <Variant name="Warning">
          <Typography.Text type="warning"></Typography.Text>
        </Variant>
        <Variant name="Danger">
          <Typography.Text type="danger"></Typography.Text>
        </Variant>
        <Variant name="Disabled">
          <Typography.Text disabled></Typography.Text>
        </Variant>
      </Component>
    </Category>
    <Category name="Layout">
      <Component name="Divider">
        <Variant>
          <Divider />
        </Variant>
      </Component>

      <Component name="Grid">
        <Variant name="Simple Row">
          <Row></Row>
        </Variant>
        <Variant name="Two columns">
          <Row>
            <Col span={12}></Col>
            <Col span={12}></Col>
          </Row>
        </Variant>
        <Variant name="Three columns">
          <Row>
            <Col span={8}></Col>
            <Col span={8}></Col>
            <Col span={8}></Col>
          </Row>
        </Variant>
      </Component>

      <Component name="Space">
        <Variant>
          <Space />
        </Variant>
        <Variant name="Small">
          <Space size={"small"} />
        </Variant>
        <Variant name="Large">
          <Space size={"large"} />
        </Variant>
      </Component>
    </Category>
    <Category name="Controls">
      <Component name="Autocomplete">
        <Variant>
          <AutoComplete placeholder="input here" />
        </Variant>
      </Component>

      <Component name="Button">
        <Variant>
          <Button></Button>
        </Variant>
        <Variant name="Primary">
          <Button type="primary"></Button>
        </Variant>
        <Variant name="Link">
          <Button type="link"></Button>
        </Variant>
        <Variant name="Dropdown">
          <Dropdown
            trigger={["click"]}
            overlay={
              <Menu>
                <Menu.Item></Menu.Item>
                <Menu.Item></Menu.Item>
                <Menu.Item></Menu.Item>
              </Menu>
            }
          >
            <Button></Button>
          </Dropdown>
        </Variant>
      </Component>

      <Component name="Checkbox">
        <Variant>
          <Checkbox />
        </Variant>
      </Component>

      <Component name="Switch">
        <Variant>
          <Switch />
        </Variant>
      </Component>

      <Component name="Radio Group">
        <Variant>
          <Radio.Group>
            <Radio value={1}>A</Radio>
            <Radio value={2}>B</Radio>
            <Radio value={3}>C</Radio>
            <Radio value={4}>D</Radio>
          </Radio.Group>
        </Variant>
        <Variant name="Button">
          <Radio.Group>
            <Radio.Button value={1}>A</Radio.Button>
            <Radio.Button value={2}>B</Radio.Button>
            <Radio.Button value={3}>C</Radio.Button>
            <Radio.Button value={4}>D</Radio.Button>
          </Radio.Group>
        </Variant>
      </Component>

      <Component name="DatePicker">
        <Variant>
          <DatePicker />
        </Variant>
        <Variant name="Range">
          <DatePicker.RangePicker />
        </Variant>
      </Component>

      <Component name="TimePicker">
        <Variant>
          <TimePicker />
        </Variant>
        <Variant name="Range">
          <TimePicker.RangePicker />
        </Variant>
      </Component>

      <Component name="Input">
        <Variant>
          <Input />
        </Variant>
        <Variant name="Number">
          <InputNumber />
        </Variant>
      </Component>

      <Component name="Select">
        <Variant>
          <Select defaultValue="1">
            <Select.Option value="1">1</Select.Option>
            <Select.Option value="2">2</Select.Option>
          </Select>
        </Variant>
        <Variant name="Multiple">
          <Select defaultValue={["1"]} mode="multiple" allowClear>
            <Select.Option value="1">1</Select.Option>
            <Select.Option value="2">2</Select.Option>
          </Select>
        </Variant>
      </Component>

      <Component name="Link">
        <Variant>
          <Typography.Link href="" target="_blank"></Typography.Link>
        </Variant>
      </Component>

      <Component name="Slider">
        <Variant>
          <Slider defaultValue={30} />
        </Variant>
        <Variant name="Range">
          <Slider range defaultValue={[20, 50]} />
        </Variant>
      </Component>
    </Category>
    <Category name="Data Display">
      <Component name="Field">
        <Variant>
          <Field
            entityName={ENTITY_NAME}
            disabled={readOnlyMode}
            propertyName=""
            formItemProps={{
              style: { marginBottom: "12px" }
            }}
          />
        </Variant>
      </Component>
      <Component name="Card">
        <Variant>
          <Card />
        </Variant>
        <Variant name="With Title">
          <Card>
            <Card title="Card title">
              <p>Card content</p>
            </Card>
          </Card>
        </Variant>
        <Variant name="My custom card">
          <Card>
            <Card title="Card title">
              <p>Card content</p>
              <Avatar />
            </Card>
          </Card>
        </Variant>
      </Component>
      <Component name="Tabs">
        <Variant>
          <Tabs defaultActiveKey="1">
            <Tabs.TabPane tab="Tab 1" key="1">
              Content of Tab Pane 1
            </Tabs.TabPane>
            <Tabs.TabPane tab="Tab 2" key="2">
              Content of Tab Pane 2
            </Tabs.TabPane>
            <Tabs.TabPane tab="Tab 3" key="3">
              Content of Tab Pane 3
            </Tabs.TabPane>
          </Tabs>
        </Variant>
        <Variant name="Tab Pane">
          <Tabs.TabPane></Tabs.TabPane>
        </Variant>
      </Component>
      <Component name="Collapse">
        <Variant>
          <Collapse defaultActiveKey="1">
            <Collapse.Panel
              header="This is panel header 1"
              key="1"
            ></Collapse.Panel>
            <Collapse.Panel
              header="This is panel header 2"
              key="2"
            ></Collapse.Panel>
            <Collapse.Panel
              header="This is panel header 3"
              key="3"
            ></Collapse.Panel>
          </Collapse>
        </Variant>
      </Component>
      <Component name="Image">
        <Variant>
          <Image width={200} src="" />
        </Variant>
      </Component>
      <Component name="Avatar">
        <Variant>
          <Avatar icon={<UserOutlined />} />
        </Variant>
        <Variant name="Image">
          <Avatar src="https://joeschmoe.io/api/v1/random" />
        </Variant>
      </Component>
      <Component name="Badge">
        <Variant>
          <Badge count={1}></Badge>
        </Variant>
      </Component>
      <Component name="Statistic">
        <Variant>
          <Statistic title="Title" value={112893} />
        </Variant>
      </Component>
      <Component name="Alert">
        <Variant name="Success">
          <Alert message="Text" type="success" />
        </Variant>
        <Variant name="Info">
          <Alert message="Text" type="info" />
        </Variant>
        <Variant name="Warning">
          <Alert message="Text" type="warning" />
        </Variant>
        <Variant name="Error">
          <Alert message="Text" type="error" />
        </Variant>
      </Component>
      <Component name="List">
        <Variant>
          <List
            bordered
            dataSource={[]}
            renderItem={item => <List.Item></List.Item>}
          />
        </Variant>
      </Component>
    </Category>
    <Category name="Icons">
      <Component name="Arrow">
        <Variant name="Up">
          <ArrowUpOutlined />
        </Variant>
        <Variant name="Down">
          <ArrowDownOutlined />
        </Variant>
        <Variant name="Left">
          <ArrowLeftOutlined />
        </Variant>
        <Variant name="Right">
          <ArrowRightOutlined />
        </Variant>
      </Component>
      <Component name="Question">
        <Variant>
          <QuestionOutlined />
        </Variant>
        <Variant name="Circle">
          <QuestionCircleOutlined />
        </Variant>
      </Component>
      <Component name="Plus">
        <Variant>
          <PlusOutlined />
        </Variant>
        <Variant name="Circle">
          <PlusCircleOutlined />
        </Variant>
      </Component>
      <Component name="Info">
        <Variant>
          <InfoOutlined />
        </Variant>
        <Variant name="Circle">
          <InfoCircleOutlined />
        </Variant>
      </Component>
      <Component name="Exclamation">
        <Variant>
          <ExclamationOutlined />
        </Variant>
        <Variant name="Circle">
          <ExclamationCircleOutlined />
        </Variant>
      </Component>
      <Component name="Close">
        <Variant>
          <CloseOutlined />
        </Variant>
        <Variant name="Circle">
          <CloseCircleOutlined />
        </Variant>
      </Component>
      <Component name="Check">
        <Variant>
          <CheckOutlined />
        </Variant>
        <Variant name="Circle">
          <CheckCircleOutlined />
        </Variant>
      </Component>
      <Component name="Edit">
        <Variant>
          <EditOutlined />
        </Variant>
      </Component>
      <Component name="Copy">
        <Variant>
          <CopyOutlined />
        </Variant>
      </Component>
      <Component name="Delete">
        <Variant>
          <DeleteOutlined />
        </Variant>
      </Component>
      <Component name="Bars">
        <Variant>
          <BarsOutlined />
        </Variant>
      </Component>
      <Component name="Bell">
        <Variant>
          <BellOutlined />
        </Variant>
      </Component>
      <Component name="Clear">
        <Variant>
          <ClearOutlined />
        </Variant>
      </Component>
      <Component name="Download">
        <Variant>
          <DownloadOutlined />
        </Variant>
      </Component>
      <Component name="Upload">
        <Variant>
          <UploadOutlined />
        </Variant>
      </Component>
      <Component name="Sync">
        <Variant>
          <SyncOutlined />
        </Variant>
      </Component>
      <Component name="Save">
        <Variant>
          <SaveOutlined />
        </Variant>
      </Component>
      <Component name="Search">
        <Variant>
          <SearchOutlined />
        </Variant>
      </Component>
      <Component name="Settings">
        <Variant>
          <SettingOutlined />
        </Variant>
      </Component>
      <Component name="Paperclip">
        <Variant>
          <PaperClipOutlined />
        </Variant>
      </Component>
      <Component name="Phone">
        <Variant>
          <PhoneOutlined />
        </Variant>
      </Component>
      <Component name="Mail">
        <Variant>
          <MailOutlined />
        </Variant>
      </Component>
      <Component name="Home">
        <Variant>
          <HomeOutlined />
        </Variant>
      </Component>
      <Component name="Contacts">
        <Variant>
          <ContactsOutlined />
        </Variant>
      </Component>
      <Component name="User">
        <Variant>
          <UserOutlined />
        </Variant>
        <Variant name="Add">
          <UserAddOutlined />
        </Variant>
        <Variant name="Remove">
          <UserDeleteOutlined />
        </Variant>
      </Component>
      <Component name="Team">
        <Variant>
          <TeamOutlined />
        </Variant>
      </Component>
    </Category>
    <Category name="Screens">
      <Component name="ExampleCustomScreen">
        <Variant>
          <ExampleCustomScreen />
        </Variant>
      </Component>
      <Component name="CustomEntityFilterTest">
        <Variant>
          <CustomEntityFilterTest />
        </Variant>
      </Component>
      <Component name="CustomFormControls">
        <Variant>
          <CustomFormControls />
        </Variant>
      </Component>
      <Component name="CustomDataDisplayComponents">
        <Variant>
          <CustomDataDisplayComponents />
        </Variant>
      </Component>
      <Component name="CustomAppLayouts">
        <Variant>
          <CustomAppLayouts />
        </Variant>
      </Component>
      <Component name="CustomControls">
        <Variant>
          <CustomControls />
        </Variant>
      </Component>
      <Component name="ErrorBoundaryTests">
        <Variant>
          <ErrorBoundaryTests />
        </Variant>
      </Component>
      <Component name="TestBlankScreen">
        <Variant>
          <TestBlankScreen />
        </Variant>
      </Component>
      <Component name="CarEditor">
        <Variant>
          <CarEditor />
        </Variant>
      </Component>
      <Component name="CarBrowserCards">
        <Variant>
          <CarBrowserCards />
        </Variant>
      </Component>
      <Component name="CarBrowserList">
        <Variant>
          <CarBrowserList />
        </Variant>
      </Component>
      <Component name="CarBrowserTable">
        <Variant>
          <CarBrowserTable />
        </Variant>
      </Component>
      <Component name="CarCardsGrid">
        <Variant>
          <CarCardsGrid />
        </Variant>
      </Component>
      <Component name="FavoriteCars">
        <Variant>
          <FavoriteCars />
        </Variant>
      </Component>
      <Component name="CarCardsWithDetails">
        <Variant>
          <CarCardsWithDetails />
        </Variant>
      </Component>
      <Component name="CarTableWithFilters">
        <Variant>
          <CarTableWithFilters />
        </Variant>
      </Component>
      <Component name="CarMasterDetail">
        <Variant>
          <CarMasterDetail />
        </Variant>
      </Component>
      <Component name="FormWizardCompositionO2O">
        <Variant>
          <FormWizardCompositionO2O />
        </Variant>
      </Component>
      <Component name="FormWizardEditor">
        <Variant>
          <FormWizardEditor />
        </Variant>
      </Component>
      <Component name="FormWizardBrowserTable">
        <Variant>
          <FormWizardBrowserTable />
        </Variant>
      </Component>
      <Component name="CarMultiSelectionTable">
        <Variant>
          <CarMultiSelectionTable />
        </Variant>
      </Component>
      <Component name="DatatypesTestEditor">
        <Variant>
          <DatatypesTestEditor />
        </Variant>
      </Component>
      <Component name="DatatypesTestBrowserCards">
        <Variant>
          <DatatypesTestBrowserCards />
        </Variant>
      </Component>
      <Component name="DatatypesTestBrowserList">
        <Variant>
          <DatatypesTestBrowserList />
        </Variant>
      </Component>
      <Component name="DatatypesTestBrowserTable">
        <Variant>
          <DatatypesTestBrowserTable />
        </Variant>
      </Component>
      <Component name="DatatypesTestCards">
        <Variant>
          <DatatypesTestCards />
        </Variant>
      </Component>
      <Component name="AssociationO2OEditor">
        <Variant>
          <AssociationO2OEditor />
        </Variant>
      </Component>
      <Component name="AssociationO2OBrowserTable">
        <Variant>
          <AssociationO2OBrowserTable />
        </Variant>
      </Component>
      <Component name="AssociationO2MEditor">
        <Variant>
          <AssociationO2MEditor />
        </Variant>
      </Component>
      <Component name="AssociationO2MBrowserTable">
        <Variant>
          <AssociationO2MBrowserTable />
        </Variant>
      </Component>
      <Component name="AssociationM2OEditor">
        <Variant>
          <AssociationM2OEditor />
        </Variant>
      </Component>
      <Component name="AssociationM2OBrowserTable">
        <Variant>
          <AssociationM2OBrowserTable />
        </Variant>
      </Component>
      <Component name="AssociationM2MEditor">
        <Variant>
          <AssociationM2MEditor />
        </Variant>
      </Component>
      <Component name="AssociationM2MBrowserTable">
        <Variant>
          <AssociationM2MBrowserTable />
        </Variant>
      </Component>
      <Component name="CompositionO2OEditor">
        <Variant>
          <CompositionO2OEditor />
        </Variant>
      </Component>
      <Component name="CompositionO2OBrowserTable">
        <Variant>
          <CompositionO2OBrowserTable />
        </Variant>
      </Component>
      <Component name="CompositionO2MEditor">
        <Variant>
          <CompositionO2MEditor />
        </Variant>
      </Component>
      <Component name="CompositionO2MBrowserTable">
        <Variant>
          <CompositionO2MBrowserTable />
        </Variant>
      </Component>
      <Component name="DeeplyNestedTestEntityEditor">
        <Variant>
          <DeeplyNestedTestEntityEditor />
        </Variant>
      </Component>
      <Component name="DeeplyNestedO2MTestEntityTable">
        <Variant>
          <DeeplyNestedO2MTestEntityTable />
        </Variant>
      </Component>
      <Component name="DeeplyNestedO2MTestEntityEditor">
        <Variant>
          <DeeplyNestedO2MTestEntityEditor />
        </Variant>
      </Component>
      <Component name="IntIdEditor">
        <Variant>
          <IntIdEditor />
        </Variant>
      </Component>
      <Component name="IntIdBrowserTable">
        <Variant>
          <IntIdBrowserTable />
        </Variant>
      </Component>
      <Component name="IntIdBrowserCards">
        <Variant>
          <IntIdBrowserCards />
        </Variant>
      </Component>
      <Component name="IntIdBrowserList">
        <Variant>
          <IntIdBrowserList />
        </Variant>
      </Component>
      <Component name="IntIdentityIdCards">
        <Variant>
          <IntIdentityIdCards />
        </Variant>
      </Component>
      <Component name="IntIdentityIdEditor">
        <Variant>
          <IntIdentityIdEditor />
        </Variant>
      </Component>
      <Component name="IntIdentityIdBrowserTable">
        <Variant>
          <IntIdentityIdBrowserTable />
        </Variant>
      </Component>
      <Component name="IntIdentityIdBrowserCards">
        <Variant>
          <IntIdentityIdBrowserCards />
        </Variant>
      </Component>
      <Component name="IntIdentityIdBrowserList">
        <Variant>
          <IntIdentityIdBrowserList />
        </Variant>
      </Component>
      <Component name="StringIdCards">
        <Variant>
          <StringIdCards />
        </Variant>
      </Component>
      <Component name="StringIdMgtCardsEdit">
        <Variant>
          <StringIdMgtCardsEdit />
        </Variant>
      </Component>
      <Component name="StringIdBrowserCards">
        <Variant>
          <StringIdBrowserCards />
        </Variant>
      </Component>
      <Component name="StringIdBrowserList">
        <Variant>
          <StringIdBrowserList />
        </Variant>
      </Component>
      <Component name="StringIdBrowserTable">
        <Variant>
          <StringIdBrowserTable />
        </Variant>
      </Component>
      <Component name="WeirdStringIdEditor">
        <Variant>
          <WeirdStringIdEditor />
        </Variant>
      </Component>
      <Component name="WeirdStringIdBrowserCards">
        <Variant>
          <WeirdStringIdBrowserCards />
        </Variant>
      </Component>
      <Component name="WeirdStringIdBrowserList">
        <Variant>
          <WeirdStringIdBrowserList />
        </Variant>
      </Component>
      <Component name="WeirdStringIdBrowserTable">
        <Variant>
          <WeirdStringIdBrowserTable />
        </Variant>
      </Component>
      <Component name="BoringStringIdEditor">
        <Variant>
          <BoringStringIdEditor />
        </Variant>
      </Component>
      <Component name="BoringStringIdBrowserTable">
        <Variant>
          <BoringStringIdBrowserTable />
        </Variant>
      </Component>
      <Component name="TrickyIdEditor">
        <Variant>
          <TrickyIdEditor />
        </Variant>
      </Component>
      <Component name="TrickyIdBrowserTable">
        <Variant>
          <TrickyIdBrowserTable />
        </Variant>
      </Component>
    </Category>
  </Palette>
)
Example #26
Source File: index.tsx    From posthog-foss with MIT License 4 votes vote down vote up
export function PreflightCheck(): JSX.Element {
    const { preflight, preflightLoading, preflightMode } = useValues(preflightLogic)
    const { setPreflightMode } = useActions(preflightLogic)
    const isReady =
        preflight &&
        preflight.django &&
        preflight.db &&
        preflight.redis &&
        preflight.celery &&
        (preflightMode === 'experimentation' || preflight.plugins)

    const checks = [
        {
            id: 'database',
            name: 'Database (Postgres)',
            status: preflight?.db,
        },
        {
            id: 'backend',
            name: 'Backend server (Django)',
            status: preflight?.django,
        },
        {
            id: 'redis',
            name: 'Cache & queue (Redis)',
            status: preflight?.redis,
        },
        {
            id: 'celery',
            name: 'Background jobs (Celery)',
            status: preflight?.celery,
        },
        {
            id: 'plugins',
            name: 'Plugin server (Node)',
            status: preflight?.plugins,
            caption: preflightMode === 'experimentation' ? 'Required in production environments' : '',
            failedState: preflightMode === 'experimentation' ? 'warning' : 'error',
        },
        {
            id: 'frontend',
            name: 'Frontend build (Webpack)',
            status: true, // If this code is ran, the front-end is already built
        },
        {
            id: 'tls',
            name: 'SSL/TLS certificate',
            status: window.location.protocol === 'https:',
            caption:
                preflightMode === 'experimentation'
                    ? 'Not required for development or testing'
                    : 'Install before ingesting real user data',
            failedState: preflightMode === 'experimentation' ? 'not-required' : 'warning',
        },
    ] as CheckInterface[]

    const handlePreflightFinished = (): void => {
        router.actions.push(urls.signup())
    }

    return (
        <div style={{ minHeight: '100vh' }}>
            <Row
                style={{
                    display: 'flex',
                    justifyContent: 'center',
                    paddingTop: 32,
                    paddingBottom: 32,
                    backgroundColor: '#eeefe9',
                }}
            >
                <img src={posthogLogo} style={{ width: 157, height: 30 }} />
            </Row>
            <Row style={{ display: 'flex', justifyContent: 'center', paddingBottom: 16 }}>
                <PageHeader title="Lets get started..." />
            </Row>
            <Row style={{ display: 'flex', justifyContent: 'center' }}>
                <div style={{ width: 960 }}>
                    <Steps current={0}>
                        <Step title="Preflight check" subTitle="1 min" description="Prepare your instance" />
                        <Step
                            title="Event capture"
                            subTitle="15 mins"
                            description="Set up your app to capture events"
                        />
                        <Step
                            title="Setup your team"
                            subTitle="5 mins"
                            description="Invite your team and start discovering insights"
                        />
                    </Steps>
                </div>
            </Row>
            <Row style={{ display: 'flex', justifyContent: 'center' }}>
                <div style={{ display: 'flex', alignItems: 'center', flexDirection: 'column' }}>
                    <img src={suprisedHog} style={{ maxHeight: '100%', width: 320 }} />
                    <p>Any questions?</p>
                    <Button type="default" data-attr="support" data-source="preflight">
                        <a href="https://posthog.com/support" target="_blank" rel="noreferrer">
                            Get support
                        </a>
                    </Button>
                </div>
                <div
                    style={{
                        display: 'flex',
                        justifyContent: 'flex-start',
                        margin: '0 32px',
                        flexDirection: 'column',
                        paddingTop: 32,
                    }}
                >
                    <Card style={{ width: '100%' }}>
                        <Row style={{ display: 'flex', justifyContent: 'space-between', lineHeight: '32px' }}>
                            {!preflightMode ? (
                                <b style={{ fontSize: 16 }}>Select launch mode</b>
                            ) : (
                                <>
                                    <b style={{ fontSize: 16 }}>
                                        <span>
                                            <span
                                                style={{ color: blue.primary, cursor: 'pointer' }}
                                                onClick={() => setPreflightMode(null)}
                                            >
                                                Select launch mode
                                            </span>{' '}
                                            &gt; {capitalizeFirstLetter(preflightMode)}
                                        </span>
                                    </b>
                                    <Button
                                        type="default"
                                        data-attr="preflight-refresh"
                                        icon={<SyncOutlined />}
                                        onClick={() => window.location.reload()}
                                        disabled={preflightLoading || !preflight}
                                    >
                                        Refresh
                                    </Button>
                                </>
                            )}
                        </Row>
                        {!preflightMode && (
                            <div>We're excited to have you here. What's your plan for this installation?</div>
                        )}
                        <div
                            className="text-center"
                            style={{ padding: '24px 0', display: 'flex', justifyContent: 'center', maxWidth: 533 }}
                        >
                            {!preflightMode && (
                                <>
                                    <Button
                                        type="default"
                                        data-attr="preflight-experimentation"
                                        size="large"
                                        onClick={() => setPreflightMode('experimentation')}
                                        icon={<ApiTwoTone />}
                                    >
                                        Just playing
                                    </Button>
                                    <Button
                                        type="primary"
                                        style={{ marginLeft: 16 }}
                                        size="large"
                                        data-attr="preflight-live"
                                        onClick={() => setPreflightMode('live')}
                                        icon={<RocketFilled />}
                                    >
                                        Live implementation
                                    </Button>
                                </>
                            )}

                            {preflightMode && (
                                <>
                                    <Row>
                                        {checks.map((item) => (
                                            <PreflightItem key={item.id} {...item} />
                                        ))}
                                    </Row>
                                </>
                            )}
                        </div>
                    </Card>
                    {preflightMode && (
                        <>
                            <div className="space-top text-center" data-attr="preflightStatus">
                                {isReady ? (
                                    <b style={{ color: green.primary }}>All systems go!</b>
                                ) : (
                                    <b>Checks in progress…</b>
                                )}
                            </div>
                            <div className="text-center" style={{ marginBottom: 64 }}>
                                <Button
                                    type="primary"
                                    data-attr="preflight-complete"
                                    data-source={preflightMode}
                                    disabled={!isReady}
                                    onClick={handlePreflightFinished}
                                >
                                    Continue
                                </Button>
                            </div>
                        </>
                    )}
                </div>
            </Row>
        </div>
    )
}
Example #27
Source File: workers.tsx    From leek with Apache License 2.0 4 votes vote down vote up
WorkersPage = () => {
  // STATE
  const service = new WorkerService();
  const { currentApp, currentEnv } = useApplication();
  const [qpHostname, setQPHostname] = useQueryParam("hostname", StringParam);

  // Data
  const columns = WorkerDataColumns();
  const [workers, setWorkers] = useState<any[]>([]);
  const [loading, setLoading] = useState<boolean>();
  const [currentWorker, setCurrentWorker] = useState({});
  const [pagination, setPagination] = useState<any>({
    pageSize: 10,
    current: 1,
  });

  // Controls
  const [stateFilter, setStateFilter] = useState<string | null>("HEARTBEAT");

  // UI
  const [workerDetailsVisible, setDetailsVisible] = useState(false);

  /** =======================
     *  Calls
     ---------------------- **/
  function filterWorkers(pager = { current: 1, pageSize: 10 }) {
    if (!currentApp) return;
    setLoading(true);
    let from_ = (pager.current - 1) * pager.pageSize;
    service
      .filter(currentApp, currentEnv, null, pager.pageSize, from_, stateFilter)
      .then(handleAPIResponse)
      .then((result: any) => {
        // Prepare pagination
        let p = fixPagination(result.hits.total.value, pager, filterWorkers);
        if (p) setPagination(p);
        else return;
        // Result
        let workersList: { any }[] = [];
        result.hits.hits.forEach(function (hit) {
          workersList.push(hit._source);
        });
        setWorkers(workersList);
      }, handleAPIError)
      .catch(handleAPIError)
      .finally(() => setLoading(false));
  }

  function getWorkerByHostname(hostname: string) {
    if (!currentApp) return;
    service
      .getById(currentApp, hostname)
      .then(handleAPIResponse)
      .then((result: any) => {
        if (result.hits.total == 0) {
          message.warning(
            "Worker not found maybe it's expired and got deleted from memory"
          );
          return;
        }
        handleShowWorkerDetails(result.hits.hits[0]._source);
      }, handleAPIError)
      .catch(handleAPIError);
  }

  /** ======================
     *  Hooks
     ---------------------- */
  useEffect(() => {
    refresh(pagination);
  }, [stateFilter, currentApp]);

  useEffect(() => {
    if (qpHostname) {
      getWorkerByHostname(qpHostname);
    }
  }, []);

  /** ======================
     *  UI Callbacks
     ---------------------- */
  function handleWorkerDetailsDrawerClosed() {
    setDetailsVisible(false);
    setQPHostname(undefined);
  }

  function handleRefreshWorkers() {
    refresh();
  }

  function handleShowTotal(total) {
    return `Total of ${total} workers`;
  }

  function handleTableChange(pagination, filters, sorter) {
    refresh(pagination);
  }

  function handleStateFilterChange(e) {
    setStateFilter(e.target.value);
  }

  function handleShowWorkerDetails(worker) {
    setCurrentWorker(worker);
    setQPHostname(worker.hostname);
    setDetailsVisible(true);
  }

  function refresh(pager = { current: 1, pageSize: 10 }) {
    setWorkers([]);
    filterWorkers(pager);
  }

  return (
    <>
      <Helmet>
        <html lang="en" />
        <title>Workers</title>
        <meta name="description" content="List of workers" />
        <meta name="keywords" content="celery, workers" />
      </Helmet>
      <Row>
        <Card
          style={{ width: "100%" }}
          bodyStyle={{ paddingBottom: 0, paddingRight: 0, paddingLeft: 0 }}
          title={
            <Row align="middle">
              <Col span={21}>
                <Radio.Group
                  onChange={handleStateFilterChange}
                  defaultValue="HEARTBEAT"
                  size="small"
                  style={{ fontWeight: 400 }}
                >
                  <Radio.Button value="" style={{ fontStyle: "normal" }}>
                    ANY
                  </Radio.Button>
                  <Radio.Button value="HEARTBEAT">HEARTBEAT</Radio.Button>
                  <Radio.Button value="ONLINE">ONLINE</Radio.Button>
                  <Radio.Button value="OFFLINE">OFFLINE</Radio.Button>
                </Radio.Group>
              </Col>

              <Col span={3}>
                <Button
                  onClick={handleRefreshWorkers}
                  icon={<SyncOutlined />}
                  style={{ float: "right" }}
                  size="small"
                />
              </Col>
            </Row>
          }
          size="small"
        >
          <Table
            dataSource={workers}
            columns={columns}
            loading={loading}
            size="small"
            rowKey="hostname"
            style={{ width: "100%" }}
            scroll={{ x: "100%" }}
            showHeader={false}
            pagination={{ ...pagination, showTotal: handleShowTotal }}
            onChange={handleTableChange}
            locale={{
              emptyText: (
                <div style={{ textAlign: "center" }}>
                  <Empty
                    image={Empty.PRESENTED_IMAGE_SIMPLE}
                    description={
                      <span>
                        No <a href="#API">workers</a> found
                      </span>
                    }
                  />
                </div>
              ),
            }}
            onRow={(record, rowIndex) => {
              return {
                onClick: (event) => {
                  handleShowWorkerDetails(record);
                },
              };
            }}
          />
        </Card>
      </Row>
      <Drawer
        width="50vw"
        placement="right"
        closable={false}
        onClose={handleWorkerDetailsDrawerClosed}
        visible={workerDetailsVisible}
      >
        <WorkerDetailsDrawer worker={currentWorker} />
      </Drawer>
    </>
  );
}
Example #28
Source File: tasks.tsx    From leek with Apache License 2.0 4 votes vote down vote up
TasksPage: React.FC = () => {
  // STATE
  const service = new TaskService();
  const controlService = new ControlService();
  const { currentApp, currentEnv } = useApplication();

  // Filters
  const [filters, setFilters] = useState<any>();
  const [timeFilters, setTimeFilters] = useState<any>({
    timestamp_type: "timestamp",
    interval_type: "past",
    offset: 900000,
  });
  const [order, setOrder] = useState<string>("desc");

  // Data
  const columns = TaskDataColumns();
  const [pagination, setPagination] = useState<any>({
    pageSize: 20,
    current: 1,
  });
  const [loading, setLoading] = useState<boolean>();
  const [tasksRetrying, setTasksRetrying] = useState<boolean>();
  const [tasks, setTasks] = useState<any[]>([]);

  // API Calls
  function filterTasks(pager = { current: 1, pageSize: 20 }) {
    if (!currentApp) return;
    setLoading(true);
    let allFilters = {
      ...filters,
      ...timeFilters,
    };
    let from_ = (pager.current - 1) * pager.pageSize;
    service
      .filter(currentApp, currentEnv, pager.pageSize, from_, order, allFilters)
      .then(handleAPIResponse)
      .then((result: any) => {
        // Prepare pagination
        let p = fixPagination(result.hits.total.value, pager, filterTasks);
        if (p) setPagination(p);
        else return;
        // Result
        let tasksList: { any }[] = [];
        result.hits.hits.forEach(function (hit) {
          tasksList.push(hit._source);
        });
        setTasks(tasksList);
      }, handleAPIError)
      .catch(handleAPIError)
      .finally(() => setLoading(false));
  }

  // Hooks
  useEffect(() => {
    refresh(pagination);
  }, [currentApp, currentEnv, filters, timeFilters, order]);

  useEffect(() => {
    //console.log(tasks)
  }, [tasks]);

  // UI Callbacks
  function refresh(pager = { current: 1, pageSize: 20 }) {
    filterTasks(pager);
  }

  function handleShowTaskDetails(record) {
    window.open(
      `/task?app=${currentApp}&env=${currentEnv}&uuid=${record.uuid}`,
      "_blank"
    );
  }

  function handleRefresh() {
    refresh(pagination);
  }

  function handleShowTotal(total) {
    return `Total of ${total} tasks`;
  }

  function handleTableChange(pagination, filters, sorter) {
    refresh(pagination);
  }

  function handleFilterChange(values) {
    setFilters(values);
  }

  function prepareList(items) {
    return (
      <List
        header={
          <Row justify="center">
            <Text strong>Ineligible Tasks IDs</Text>
          </Row>
        }
        dataSource={items}
        style={{ maxHeight: 200, overflow: "auto" }}
        size="small"
        bordered
        renderItem={(item) => <List.Item>{item}</List.Item>}
      />
    );
  }

  function bulkRetryConfirmation(result) {
    if (result.eligible_tasks_count == 0) {
      message.warning("Found no eligible tasks for retrying!");
      return;
    }
    confirm({
      title: "Retry Filtered Tasks",
      icon: <ExclamationCircleOutlined />,
      content: (
        <>
          <Typography.Paragraph>
            Do you really want to retry filtered tasks?
            <ul>
              <li>
                {result.eligible_tasks_count} tasks are eligible to be retried.
              </li>
              <li>
                {result.ineligible_tasks_count} tasks are not eligible to be
                retried.
              </li>
            </ul>
          </Typography.Paragraph>
          {result.ineligible_tasks_count > 0 &&
            prepareList(result.ineligible_tasks_ids)}
        </>
      ),
      onOk() {
        return retryFiltered(false);
      },
    });
  }

  function pendingBulkRetry(result) {
    confirm({
      title: "Bulk tasks retry initiated!",
      icon: <CheckCircleOutlined style={{ color: "#00BFA6" }} />,
      content: (
        <>
          <Typography.Paragraph>
            Tasks queued to the broker, you can filter the retried tasks using
            the client name.
            <ul>
              <li>
                Client name:{" "}
                <Text copyable code>
                  {result.origin}
                </Text>
              </li>
              <li>{result.succeeded_retries_count} tasks set to retry.</li>
              <li>{result.failed_retries_count} tasks could not be retried.</li>
            </ul>
          </Typography.Paragraph>
          {result.failed_retries_count > 0 &&
            prepareList(result.failed_retries)}
        </>
      ),
      okText: "Ok",
      cancelButtonProps: { style: { display: "none" } },
    });
  }

  function retryFiltered(dryRun) {
    if (!currentApp || !currentEnv) return;
    setTasksRetrying(true);
    let allFilters = { ...filters, ...timeFilters };
    return controlService
      .retryTasksByQuery(currentApp, currentEnv, allFilters, dryRun)
      .then(handleAPIResponse)
      .then((result: any) => {
        if (dryRun) {
          bulkRetryConfirmation(result);
        } else {
          pendingBulkRetry(result);
        }
      }, handleAPIError)
      .catch(handleAPIError)
      .finally(() => setTasksRetrying(false));
  }

  function handleRetryFiltered() {
    retryFiltered(true);
  }

  return (
    <>
      <Helmet>
        <html lang="en" />
        <title>Tasks</title>
        <meta name="description" content="List of tasks" />
        <meta name="keywords" content="celery, tasks" />
      </Helmet>

      <Row style={{ marginBottom: "16px" }} gutter={[12, 12]}>
        <Col xxl={5} xl={6} md={7} lg={8} sm={24} xs={24}>
          <AttributesFilter
            onFilter={handleFilterChange}
            filters={timeFilters}
          />
        </Col>
        <Col xxl={19} xl={18} md={17} lg={16} sm={24} xs={24}>
          <Row justify="center" style={{ width: "100%" }}>
            <Card
              bodyStyle={{ paddingBottom: 0, paddingRight: 0, paddingLeft: 0 }}
              size="small"
              style={{ width: "100%" }}
              title={
                <Row align="middle">
                  <Col span={3}>
                    <Switch
                      defaultChecked={order == "desc"}
                      style={{ marginLeft: "10px" }}
                      onChange={(e) => {
                        setOrder(e ? "desc" : "asc");
                      }}
                      size="small"
                      checkedChildren={
                        <CaretUpOutlined style={{ color: "#333" }} />
                      }
                      unCheckedChildren={<CaretDownOutlined />}
                    />
                  </Col>
                  <Col span={18} style={{ textAlign: "center" }}>
                    <TimeFilter
                      timeFilter={timeFilters}
                      onTimeFilterChange={setTimeFilters}
                    />
                  </Col>
                  <Col span={3}>
                    <Space style={{ float: "right" }}>
                      {filters &&
                        filters.state &&
                        filters.state.length &&
                        filters.state.every((s) =>
                          TerminalStates.includes(s)
                        ) && (
                          <Button
                            ghost
                            type="primary"
                            size="small"
                            onClick={handleRetryFiltered}
                            loading={tasksRetrying}
                          >
                            Retry Filtered
                          </Button>
                        )}
                      <Button
                        size="small"
                        onClick={handleRefresh}
                        icon={<SyncOutlined />}
                      />
                    </Space>
                  </Col>
                </Row>
              }
            >
              <Table
                dataSource={tasks}
                columns={columns}
                pagination={{ ...pagination, showTotal: handleShowTotal }}
                loading={loading}
                size="small"
                rowKey="uuid"
                showHeader={false}
                onChange={handleTableChange}
                style={{ width: "100%" }}
                scroll={{ x: "100%" }}
                locale={{
                  emptyText: (
                    <div style={{ textAlign: "center" }}>
                      <Empty
                        image={Empty.PRESENTED_IMAGE_SIMPLE}
                        description={
                          <span>
                            No <a href="#API">tasks</a> found
                          </span>
                        }
                      />
                    </div>
                  ),
                }}
                onRow={(record, rowIndex) => {
                  return {
                    onClick: (event) => {
                      handleShowTaskDetails(record);
                    },
                  };
                }}
              />
            </Card>
          </Row>
        </Col>
      </Row>
    </>
  );
}
Example #29
Source File: queues.tsx    From leek with Apache License 2.0 4 votes vote down vote up
QueuesPage = () => {
  const columns = QueueDataColumns();
  const service = new QueueService();
  const [loading, setLoading] = useState<boolean>();
  const [queues, setQueues] = useState<any>([]);

  const { currentApp, currentEnv } = useApplication();
  const [pagination, setPagination] = useState<any>({
    pageSize: 10,
    current: 1,
  });
  const [timeFilters, setTimeFilters] = useState<any>({
    timestamp_type: "timestamp",
    interval_type: "past",
    offset: 900000,
  });

  function filterQueues(pager = { current: 1, pageSize: 10 }) {
    if (!currentApp) return;
    setLoading(true);
    service
      .filter(currentApp, currentEnv, timeFilters)
      .then(handleAPIResponse)
      .then((result: any) => {
        setQueues(
          result.aggregations.queues.buckets.map(
            ({ key, doc_count, state }) => {
              let tasksStatesSeries = {
                QUEUED: 0,
                RECEIVED: 0,
                STARTED: 0,
                SUCCEEDED: 0,
                FAILED: 0,
                REJECTED: 0,
                REVOKED: 0,
                RETRY: 0,
                RECOVERED: 0,
                CRITICAL: 0,
              };
              const states = state.buckets.reduce((result, item) => {
                result[item.key] = item.doc_count;
                return result;
              }, tasksStatesSeries);
              return {
                queue: key,
                doc_count: doc_count,
                ...states,
              };
            }
          )
        );
      }, handleAPIError)
      .catch(handleAPIError)
      .finally(() => setLoading(false));
  }

  useEffect(() => {
    refresh(pagination);
  }, [currentApp, currentEnv, timeFilters]);

  // UI Callbacks
  function refresh(pager = { current: 1, pageSize: 10 }) {
    filterQueues(pager);
  }

  function handleShowTotal(total) {
    return `Total of ${total} queues`;
  }

  function handleRefresh() {
    refresh(pagination);
  }

  return (
    <>
      <Helmet>
        <html lang="en" />
        <title>Queues</title>
        <meta name="description" content="Broker queues" />
        <meta name="keywords" content="celery, queues" />
      </Helmet>
      <Row justify="center" style={{ width: "100%", marginTop: 13 }}>
        <Alert
          type="warning"
          showIcon
          closable
          message="For monitoring queues, you should enable task_send_sent_event celery parameter on clients level!"
          action={
            <a
              target="_blank"
              rel="noopener norefferer"
              href="https://tryleek.com/docs/introduction/requirements#enable-celery-task_send_sent_event"
            >
              <Button size="small" type="text">
                Details
              </Button>
            </a>
          }
        />
      </Row>
      <Row justify="center" style={{ width: "100%", marginTop: 13 }}>
        <Card
          bodyStyle={{ paddingBottom: 0, paddingRight: 0, paddingLeft: 0 }}
          size="small"
          style={{ width: "100%" }}
          title={
            <Row align="middle">
              <Col span={3}></Col>
              <Col span={18} style={{ textAlign: "center" }}>
                <TimeFilter
                  timeFilter={timeFilters}
                  onTimeFilterChange={setTimeFilters}
                />
              </Col>
              <Col span={3}>
                <Button
                  size="small"
                  onClick={handleRefresh}
                  icon={<SyncOutlined />}
                  style={{ float: "right" }}
                />
              </Col>
            </Row>
          }
        >
          <Table
            dataSource={queues}
            columns={columns}
            loading={loading}
            pagination={{ ...pagination, showTotal: handleShowTotal }}
            size="small"
            rowKey="queue"
            style={{ width: "100%" }}
            scroll={{ x: "100%" }}
            locale={{
              emptyText: (
                <div style={{ textAlign: "center" }}>
                  <Empty
                    image={Empty.PRESENTED_IMAGE_SIMPLE}
                    description={
                      <span>
                        No <a href="#API">queues</a> found
                      </span>
                    }
                  />
                </div>
              ),
            }}
          />
        </Card>
      </Row>
    </>
  );
}