@ant-design/icons#CloudDownloadOutlined TypeScript Examples

The following examples show how to use @ant-design/icons#CloudDownloadOutlined. 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: PluginUpdateButton.tsx    From posthog-foss with MIT License 6 votes vote down vote up
PluginUpdateButton = ({ updateStatus, pluginId, rearranging }: PluginUpdateButtonProps): JSX.Element => {
    const { editPlugin, updatePlugin } = useActions(pluginsLogic)
    const { pluginsUpdating } = useValues(pluginsLogic)
    return (
        <Button
            type={updateStatus?.updated ? 'default' : 'primary'}
            className="padding-under-500"
            onClick={() => (updateStatus?.updated ? editPlugin(pluginId) : updatePlugin(pluginId))}
            loading={pluginsUpdating.includes(pluginId)}
            icon={updateStatus?.updated ? <CheckOutlined /> : <CloudDownloadOutlined />}
            disabled={rearranging}
            data-attr="plugin-update"
        >
            <span className="show-over-500">{updateStatus?.updated ? 'Updated' : 'Update'}</span>
        </Button>
    )
}
Example #2
Source File: UpdateAvailable.tsx    From posthog-foss with MIT License 6 votes vote down vote up
export function UpdateAvailable({ url, tag, latestTag }: { url: string; tag: string; latestTag: string }): JSX.Element {
    let compareUrl: string = ''

    if (url.match(/^https:\/\/(www.|)github.com\//)) {
        compareUrl = `${url}/compare/${tag}...${latestTag}`
    }
    if (url.match(/^https:\/\/(www.|)gitlab.com\//)) {
        compareUrl = `${url}/-/compare/${tag}...${latestTag}`
    }

    return (
        <Tooltip
            title={
                <div>
                    Installed: <SHATag tag={tag} />
                    <br />
                    Latest: <SHATag tag={latestTag} />
                    {compareUrl ? <div style={{ marginTop: 10 }}>Click to see the diff</div> : null}
                </div>
            }
        >
            {compareUrl ? (
                <a href={compareUrl} target="_blank" rel="noreferrer noopener">
                    <Tag color="volcano" style={{ cursor: 'pointer' }}>
                        <CloudDownloadOutlined /> Update available!
                    </Tag>
                </a>
            ) : (
                <Tag color="volcano">
                    <CloudDownloadOutlined /> Update available!
                </Tag>
            )}
        </Tooltip>
    )
}
Example #3
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 #4
Source File: ChartTypeSelector.tsx    From datart with Apache License 2.0 5 votes vote down vote up
ChartTypeSelector: FC<{
  type;
  translate: (title: string) => string;
  onChange: (value) => void;
  onCreateDownloadDataTask?: () => void;
}> = memo(({ type, onChange, onCreateDownloadDataTask }) => {
  const t = useI18NPrefix(`viz.action.common`);
  const typeChange = useCallback(
    type => () => {
      onChange(type);
    },
    [onChange],
  );

  return (
    <StyledChartTypeSelector>
      <TypeSelector
        fontSize={FONT_SIZE_HEADING}
        className={classnames({ active: type === ChartPresentType.GRAPH })}
        onClick={typeChange(ChartPresentType.GRAPH)}
      >
        <AreaChartOutlined />
      </TypeSelector>
      <TypeSelector
        fontSize={FONT_SIZE_HEADING}
        className={classnames({ active: type === ChartPresentType.RAW })}
        onClick={typeChange(ChartPresentType.RAW)}
      >
        <TableOutlined />
      </TypeSelector>
      <TypeSelector
        fontSize={FONT_SIZE_HEADING}
        className={classnames({ active: type === ChartPresentType.SQL })}
        onClick={typeChange(ChartPresentType.SQL)}
      >
        <ConsoleSqlOutlined />
      </TypeSelector>
      <TypeSelector
        fontSize={FONT_SIZE_HEADING}
        className={classnames({ active: type === ChartPresentType.DOWNLOAD })}
      >
        <Popconfirm
          placement="left"
          title={t('downloadForExcel')}
          onConfirm={onCreateDownloadDataTask}
        >
          <CloudDownloadOutlined />
        </Popconfirm>
      </TypeSelector>
    </StyledChartTypeSelector>
  );
})
Example #5
Source File: index.tsx    From datart with Apache License 2.0 5 votes vote down vote up
DownloadListPopup: FC<DownloadListPopupProps> = ({
  tooltipProps,
  polling,
  renderDom,
  setPolling,
  onLoadTasks,
  onDownloadFile,
}) => {
  const [tasks, setTasks] = useState<DownloadTask[]>([]);
  const t = useI18NPrefix('main.nav');

  const downloadableNum = useMemo(() => {
    return (tasks || []).filter(v => v.status === DownloadTaskState.DONE)
      .length;
  }, [tasks]);

  useEffect(() => {
    let id;
    if (polling && typeof id !== 'number') {
      onLoadTasks().then(({ isNeedStopPolling, data }) => {
        setTasks(data);
        if (!isNeedStopPolling) {
          id = setInterval(() => {
            onLoadTasks().then(({ isNeedStopPolling, data }) => {
              setTasks(data);
              if (isNeedStopPolling) {
                clearInterval(id);
                setPolling(false);
              }
            });
          }, DOWNLOAD_POLLING_INTERVAL);
        } else {
          setPolling(false);
        }
      });
    } else if (typeof id === 'number') {
      typeof id === 'number' && clearInterval(id);
    }
    return () => {
      typeof id === 'number' && clearInterval(id);
    };
  }, [polling, setPolling, onLoadTasks]);
  useMount(() => {
    setPolling(true);
  });

  return (
    <Popup
      content={<DownloadList onDownloadFile={onDownloadFile} tasks={tasks} />}
      trigger={['click']}
      placement="rightBottom"
    >
      <li>
        <Tooltip
          title={t('download.title')}
          placement="right"
          {...tooltipProps}
        >
          <Badge count={downloadableNum}>
            {renderDom || <CloudDownloadOutlined style={{ fontSize: 20 }} />}
          </Badge>
        </Tooltip>
      </li>
    </Popup>
  );
}
Example #6
Source File: PluginCard.tsx    From posthog-foss with MIT License 4 votes vote down vote up
export function PluginCard({
    plugin,
    error,
    maintainer,
    showUpdateButton,
    order,
    maxOrder,
    rearranging,
    DragColumn = ({ children }) => <Col className="order-handle">{children}</Col>,
    unorderedPlugin = false,
}: PluginCardProps): JSX.Element {
    const {
        name,
        description,
        url,
        plugin_type: pluginType,
        pluginConfig,
        tag,
        latest_tag: latestTag,
        id: pluginId,
        updateStatus,
        hasMoved,
        is_global,
        organization_id,
        organization_name,
    } = plugin

    const {
        editPlugin,
        toggleEnabled,
        installPlugin,
        resetPluginConfigError,
        rearrange,
        showPluginLogs,
        showPluginMetrics,
    } = useActions(pluginsLogic)
    const { loading, installingPluginUrl, checkingForUpdates, pluginUrlToMaintainer } = useValues(pluginsLogic)
    const { user } = useValues(userLogic)

    const hasSpecifiedMaintainer = maintainer || (plugin.url && pluginUrlToMaintainer[plugin.url])
    const pluginMaintainer = maintainer || pluginUrlToMaintainer[plugin.url || '']

    return (
        <Col
            style={{ width: '100%', marginBottom: 20 }}
            className={`plugins-scene-plugin-card-col${rearranging ? ` rearranging` : ''}`}
            data-attr={`plugin-card-${pluginConfig ? 'installed' : 'available'}`}
        >
            <Card className="plugins-scene-plugin-card">
                <Row align="middle" className="plugin-card-row">
                    {typeof order === 'number' && typeof maxOrder === 'number' ? (
                        <DragColumn>
                            <div className={`arrow${order === 1 ? ' hide' : ''}`}>
                                <DownOutlined />
                            </div>
                            <div>
                                <Tag color={hasMoved ? '#bd0225' : '#555'} onClick={rearrange}>
                                    {order}
                                </Tag>
                            </div>
                            <div className={`arrow${order === maxOrder ? ' hide' : ''}`}>
                                <DownOutlined />
                            </div>
                        </DragColumn>
                    ) : null}
                    {unorderedPlugin ? (
                        <Tooltip title="This plugin does not do any processing in order." placement="topRight">
                            <Col>
                                <Tag color="#555">-</Tag>
                            </Col>
                        </Tooltip>
                    ) : null}
                    {pluginConfig && (
                        <Col>
                            <Popconfirm
                                placement="topLeft"
                                title={`Are you sure you wish to ${
                                    pluginConfig.enabled ? 'disable' : 'enable'
                                } this plugin?`}
                                onConfirm={() =>
                                    pluginConfig.id
                                        ? toggleEnabled({ id: pluginConfig.id, enabled: !pluginConfig.enabled })
                                        : editPlugin(pluginId || null, { __enabled: true })
                                }
                                okText="Yes"
                                cancelText="No"
                                disabled={rearranging}
                            >
                                <Switch checked={pluginConfig.enabled ?? false} disabled={rearranging} />
                            </Popconfirm>
                        </Col>
                    )}
                    <Col className={pluginConfig ? 'hide-plugin-image-below-500' : ''}>
                        <PluginImage pluginType={pluginType} url={url} />
                    </Col>
                    <Col style={{ flex: 1 }}>
                        <div>
                            <strong style={{ marginRight: 8 }}>{name}</strong>
                            {hasSpecifiedMaintainer && (
                                <CommunityPluginTag isCommunity={pluginMaintainer === 'community'} />
                            )}
                            {pluginConfig?.error ? (
                                <PluginError
                                    error={pluginConfig.error}
                                    reset={() => resetPluginConfigError(pluginConfig?.id || 0)}
                                />
                            ) : error ? (
                                <PluginError error={error} />
                            ) : null}
                            {is_global && (
                                <Tag color="blue">
                                    <GlobalOutlined /> Managed by {organization_name}
                                </Tag>
                            )}
                            {canInstallPlugins(user?.organization, organization_id) && (
                                <>
                                    {url?.startsWith('file:') ? <LocalPluginTag url={url} title="Local" /> : null}
                                    {updateStatus?.error ? (
                                        <Tag color="red">
                                            <WarningOutlined /> Error checking for updates
                                        </Tag>
                                    ) : checkingForUpdates &&
                                      !updateStatus &&
                                      pluginType !== PluginInstallationType.Source &&
                                      !url?.startsWith('file:') ? (
                                        <Tag color="blue">
                                            <LoadingOutlined /> Checking for updates…
                                        </Tag>
                                    ) : url && latestTag && tag ? (
                                        tag === latestTag ? (
                                            <Tag color="green">
                                                <CheckOutlined /> Up to date
                                            </Tag>
                                        ) : (
                                            <UpdateAvailable url={url} tag={tag} latestTag={latestTag} />
                                        )
                                    ) : null}
                                    {pluginType === PluginInstallationType.Source ? <SourcePluginTag /> : null}
                                </>
                            )}
                        </div>
                        <div>{endWithPunctation(description)}</div>
                    </Col>
                    <Col>
                        <Space>
                            {url && <PluginAboutButton url={url} disabled={rearranging} />}
                            {showUpdateButton && pluginId ? (
                                <PluginUpdateButton
                                    updateStatus={updateStatus}
                                    pluginId={pluginId}
                                    rearranging={rearranging}
                                />
                            ) : pluginId ? (
                                <>
                                    {Object.keys(plugin.metrics || {}).length > 0 ? (
                                        <Space>
                                            <Tooltip title="Metrics">
                                                <Button onClick={() => showPluginMetrics(pluginId)}>
                                                    <LineChartOutlined />
                                                </Button>
                                            </Tooltip>
                                        </Space>
                                    ) : null}
                                    <Tooltip
                                        title={
                                            pluginConfig?.id
                                                ? 'Logs'
                                                : 'Logs – enable the plugin for the first time to view them'
                                        }
                                    >
                                        <Button
                                            className="padding-under-500"
                                            disabled={rearranging || !pluginConfig?.id}
                                            onClick={() => showPluginLogs(pluginId)}
                                            data-attr="plugin-logs"
                                        >
                                            <UnorderedListOutlined />
                                        </Button>
                                    </Tooltip>
                                    <Tooltip title="Configure">
                                        <Button
                                            type="primary"
                                            className="padding-under-500"
                                            disabled={rearranging}
                                            onClick={() => editPlugin(pluginId)}
                                            data-attr="plugin-configure"
                                        >
                                            <SettingOutlined />
                                        </Button>
                                    </Tooltip>
                                </>
                            ) : !pluginId ? (
                                <Button
                                    type="primary"
                                    className="padding-under-500"
                                    loading={loading && installingPluginUrl === url}
                                    disabled={loading && installingPluginUrl !== url}
                                    onClick={
                                        url ? () => installPlugin(url, PluginInstallationType.Repository) : undefined
                                    }
                                    icon={<CloudDownloadOutlined />}
                                    data-attr="plugin-install"
                                >
                                    <span className="show-over-500">Install</span>
                                </Button>
                            ) : null}
                        </Space>
                    </Col>
                </Row>
            </Card>
        </Col>
    )
}
Example #7
Source File: Icon.tsx    From html2sketch with MIT License 4 votes vote down vote up
IconSymbol: FC = () => {
  return (
    <Row>
      {/*<CaretUpOutlined*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/1.CaretUpOutlined'}*/}
      {/*/>*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/2.MailOutlined'}*/}
      {/*/>*/}
      {/*<StepBackwardOutlined*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
      {/*/>*/}
      {/*<StepForwardOutlined*/}
      {/*  className="icon"*/}
      {/*  symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
      {/*/>*/}
      <StepForwardOutlined />
      <ShrinkOutlined />
      <ArrowsAltOutlined />
      <DownOutlined />
      <UpOutlined />
      <LeftOutlined />
      <RightOutlined />
      <CaretUpOutlined />
      <CaretDownOutlined />
      <CaretLeftOutlined />
      <CaretRightOutlined />
      <VerticalAlignTopOutlined />
      <RollbackOutlined />
      <FastBackwardOutlined />
      <FastForwardOutlined />
      <DoubleRightOutlined />
      <DoubleLeftOutlined />
      <VerticalLeftOutlined />
      <VerticalRightOutlined />
      <VerticalAlignMiddleOutlined />
      <VerticalAlignBottomOutlined />
      <ForwardOutlined />
      <BackwardOutlined />
      <EnterOutlined />
      <RetweetOutlined />
      <SwapOutlined />
      <SwapLeftOutlined />
      <SwapRightOutlined />
      <ArrowUpOutlined />
      <ArrowDownOutlined />
      <ArrowLeftOutlined />
      <ArrowRightOutlined />
      <LoginOutlined />
      <LogoutOutlined />
      <MenuFoldOutlined />
      <MenuUnfoldOutlined />
      <BorderBottomOutlined />
      <BorderHorizontalOutlined />
      <BorderInnerOutlined />
      <BorderOuterOutlined />
      <BorderLeftOutlined />
      <BorderRightOutlined />
      <BorderTopOutlined />
      <BorderVerticleOutlined />
      <PicCenterOutlined />
      <PicLeftOutlined />
      <PicRightOutlined />
      <RadiusBottomleftOutlined />
      <RadiusBottomrightOutlined />
      <RadiusUpleftOutlined />
      <RadiusUprightOutlined />
      <FullscreenOutlined />
      <FullscreenExitOutlined />
      <QuestionOutlined />
      <PauseOutlined />
      <MinusOutlined />
      <PauseCircleOutlined />
      <InfoOutlined />
      <CloseOutlined />
      <ExclamationOutlined />
      <CheckOutlined />
      <WarningOutlined />
      <IssuesCloseOutlined />
      <StopOutlined />
      <EditOutlined />
      <CopyOutlined />
      <ScissorOutlined />
      <DeleteOutlined />
      <SnippetsOutlined />
      <DiffOutlined />
      <HighlightOutlined />
      <AlignCenterOutlined />
      <AlignLeftOutlined />
      <AlignRightOutlined />
      <BgColorsOutlined />
      <BoldOutlined />
      <ItalicOutlined />
      <UnderlineOutlined />
      <StrikethroughOutlined />
      <RedoOutlined />
      <UndoOutlined />
      <ZoomInOutlined />
      <ZoomOutOutlined />
      <FontColorsOutlined />
      <FontSizeOutlined />
      <LineHeightOutlined />
      <SortAscendingOutlined />
      <SortDescendingOutlined />
      <DragOutlined />
      <OrderedListOutlined />
      <UnorderedListOutlined />
      <RadiusSettingOutlined />
      <ColumnWidthOutlined />
      <ColumnHeightOutlined />
      <AreaChartOutlined />
      <PieChartOutlined />
      <BarChartOutlined />
      <DotChartOutlined />
      <LineChartOutlined />
      <RadarChartOutlined />
      <HeatMapOutlined />
      <FallOutlined />
      <RiseOutlined />
      <StockOutlined />
      <BoxPlotOutlined />
      <FundOutlined />
      <SlidersOutlined />
      <AndroidOutlined />
      <AppleOutlined />
      <WindowsOutlined />
      <IeOutlined />
      <ChromeOutlined />
      <GithubOutlined />
      <AliwangwangOutlined />
      <DingdingOutlined />
      <WeiboSquareOutlined />
      <WeiboCircleOutlined />
      <TaobaoCircleOutlined />
      <Html5Outlined />
      <WeiboOutlined />
      <TwitterOutlined />
      <WechatOutlined />
      <AlipayCircleOutlined />
      <TaobaoOutlined />
      <SkypeOutlined />
      <FacebookOutlined />
      <CodepenOutlined />
      <CodeSandboxOutlined />
      <AmazonOutlined />
      <GoogleOutlined />
      <AlipayOutlined />
      <AntDesignOutlined />
      <AntCloudOutlined />
      <ZhihuOutlined />
      <SlackOutlined />
      <SlackSquareOutlined />
      <BehanceSquareOutlined />
      <DribbbleOutlined />
      <DribbbleSquareOutlined />
      <InstagramOutlined />
      <YuqueOutlined />
      <AlibabaOutlined />
      <YahooOutlined />
      <RedditOutlined />
      <SketchOutlined />
      <AccountBookOutlined />
      <AlertOutlined />
      <ApartmentOutlined />
      <ApiOutlined />
      <QqOutlined />
      <MediumWorkmarkOutlined />
      <GitlabOutlined />
      <MediumOutlined />
      <GooglePlusOutlined />
      <AppstoreAddOutlined />
      <AppstoreOutlined />
      <AudioOutlined />
      <AudioMutedOutlined />
      <AuditOutlined />
      <BankOutlined />
      <BarcodeOutlined />
      <BarsOutlined />
      <BellOutlined />
      <BlockOutlined />
      <BookOutlined />
      <BorderOutlined />
      <BranchesOutlined />
      <BuildOutlined />
      <BulbOutlined />
      <CalculatorOutlined />
      <CalendarOutlined />
      <CameraOutlined />
      <CarOutlined />
      <CarryOutOutlined />
      <CiCircleOutlined />
      <CiOutlined />
      <CloudOutlined />
      <ClearOutlined />
      <ClusterOutlined />
      <CodeOutlined />
      <CoffeeOutlined />
      <CompassOutlined />
      <CompressOutlined />
      <ContactsOutlined />
      <ContainerOutlined />
      <ControlOutlined />
      <CopyrightCircleOutlined />
      <CopyrightOutlined />
      <CreditCardOutlined />
      <CrownOutlined />
      <CustomerServiceOutlined />
      <DashboardOutlined />
      <DatabaseOutlined />
      <DeleteColumnOutlined />
      <DeleteRowOutlined />
      <DisconnectOutlined />
      <DislikeOutlined />
      <DollarCircleOutlined />
      <DollarOutlined />
      <DownloadOutlined />
      <EllipsisOutlined />
      <EnvironmentOutlined />
      <EuroCircleOutlined />
      <EuroOutlined />
      <ExceptionOutlined />
      <ExpandAltOutlined />
      <ExpandOutlined />
      <ExperimentOutlined />
      <ExportOutlined />
      <EyeOutlined />
      <FieldBinaryOutlined />
      <FieldNumberOutlined />
      <FieldStringOutlined />
      <DesktopOutlined />
      <DingtalkOutlined />
      <FileAddOutlined />
      <FileDoneOutlined />
      <FileExcelOutlined />
      <FileExclamationOutlined />
      <FileOutlined />
      <FileImageOutlined />
      <FileJpgOutlined />
      <FileMarkdownOutlined />
      <FilePdfOutlined />
      <FilePptOutlined />
      <FileProtectOutlined />
      <FileSearchOutlined />
      <FileSyncOutlined />
      <FileTextOutlined />
      <FileUnknownOutlined />
      <FileWordOutlined />
      <FilterOutlined />
      <FireOutlined />
      <FlagOutlined />
      <FolderAddOutlined />
      <FolderOutlined />
      <FolderOpenOutlined />
      <ForkOutlined />
      <FormatPainterOutlined />
      <FrownOutlined />
      <FunctionOutlined />
      <FunnelPlotOutlined />
      <GatewayOutlined />
      <GifOutlined />
      <GiftOutlined />
      <GlobalOutlined />
      <GoldOutlined />
      <GroupOutlined />
      <HddOutlined />
      <HeartOutlined />
      <HistoryOutlined />
      <HomeOutlined />
      <HourglassOutlined />
      <IdcardOutlined />
      <ImportOutlined />
      <InboxOutlined />
      <InsertRowAboveOutlined />
      <InsertRowBelowOutlined />
      <InsertRowLeftOutlined />
      <InsertRowRightOutlined />
      <InsuranceOutlined />
      <InteractionOutlined />
      <KeyOutlined />
      <LaptopOutlined />
      <LayoutOutlined />
      <LikeOutlined />
      <LineOutlined />
      <LinkOutlined />
      <Loading3QuartersOutlined />
      <LoadingOutlined />
      <LockOutlined />
      <MailOutlined />
      <ManOutlined />
      <MedicineBoxOutlined />
      <MehOutlined />
      <MenuOutlined />
      <MergeCellsOutlined />
      <MessageOutlined />
      <MobileOutlined />
      <MoneyCollectOutlined />
      <MonitorOutlined />
      <MoreOutlined />
      <NodeCollapseOutlined />
      <NodeExpandOutlined />
      <NodeIndexOutlined />
      <NotificationOutlined />
      <NumberOutlined />
      <PaperClipOutlined />
      <PartitionOutlined />
      <PayCircleOutlined />
      <PercentageOutlined />
      <PhoneOutlined />
      <PictureOutlined />
      <PoundCircleOutlined />
      <PoundOutlined />
      <PoweroffOutlined />
      <PrinterOutlined />
      <ProfileOutlined />
      <ProjectOutlined />
      <PropertySafetyOutlined />
      <PullRequestOutlined />
      <PushpinOutlined />
      <QrcodeOutlined />
      <ReadOutlined />
      <ReconciliationOutlined />
      <RedEnvelopeOutlined />
      <ReloadOutlined />
      <RestOutlined />
      <RobotOutlined />
      <RocketOutlined />
      <SafetyCertificateOutlined />
      <SafetyOutlined />
      <ScanOutlined />
      <ScheduleOutlined />
      <SearchOutlined />
      <SecurityScanOutlined />
      <SelectOutlined />
      <SendOutlined />
      <SettingOutlined />
      <ShakeOutlined />
      <ShareAltOutlined />
      <ShopOutlined />
      <ShoppingCartOutlined />
      <ShoppingOutlined />
      <SisternodeOutlined />
      <SkinOutlined />
      <SmileOutlined />
      <SolutionOutlined />
      <SoundOutlined />
      <SplitCellsOutlined />
      <StarOutlined />
      <SubnodeOutlined />
      <SyncOutlined />
      <TableOutlined />
      <TabletOutlined />
      <TagOutlined />
      <TagsOutlined />
      <TeamOutlined />
      <ThunderboltOutlined />
      <ToTopOutlined />
      <ToolOutlined />
      <TrademarkCircleOutlined />
      <TrademarkOutlined />
      <TransactionOutlined />
      <TrophyOutlined />
      <UngroupOutlined />
      <UnlockOutlined />
      <UploadOutlined />
      <UsbOutlined />
      <UserAddOutlined />
      <UserDeleteOutlined />
      <UserOutlined />
      <UserSwitchOutlined />
      <UsergroupAddOutlined />
      <UsergroupDeleteOutlined />
      <VideoCameraOutlined />
      <WalletOutlined />
      <WifiOutlined />
      <BorderlessTableOutlined />
      <WomanOutlined />
      <BehanceOutlined />
      <DropboxOutlined />
      <DeploymentUnitOutlined />
      <UpCircleOutlined />
      <DownCircleOutlined />
      <LeftCircleOutlined />
      <RightCircleOutlined />
      <UpSquareOutlined />
      <DownSquareOutlined />
      <LeftSquareOutlined />
      <RightSquareOutlined />
      <PlayCircleOutlined />
      <QuestionCircleOutlined />
      <PlusCircleOutlined />
      <PlusSquareOutlined />
      <MinusSquareOutlined />
      <MinusCircleOutlined />
      <InfoCircleOutlined />
      <ExclamationCircleOutlined />
      <CloseCircleOutlined />
      <CloseSquareOutlined />
      <CheckCircleOutlined />
      <CheckSquareOutlined />
      <ClockCircleOutlined />
      <FormOutlined />
      <DashOutlined />
      <SmallDashOutlined />
      <YoutubeOutlined />
      <CodepenCircleOutlined />
      <AliyunOutlined />
      <PlusOutlined />
      <LinkedinOutlined />
      <AimOutlined />
      <BugOutlined />
      <CloudDownloadOutlined />
      <CloudServerOutlined />
      <CloudSyncOutlined />
      <CloudUploadOutlined />
      <CommentOutlined />
      <ConsoleSqlOutlined />
      <EyeInvisibleOutlined />
      <FileGifOutlined />
      <DeliveredProcedureOutlined />
      <FieldTimeOutlined />
      <FileZipOutlined />
      <FolderViewOutlined />
      <FundProjectionScreenOutlined />
      <FundViewOutlined />
      <MacCommandOutlined />
      <PlaySquareOutlined />
      <OneToOneOutlined />
      <RotateLeftOutlined />
      <RotateRightOutlined />
      <SaveOutlined />
      <SwitcherOutlined />
      <TranslationOutlined />
      <VerifiedOutlined />
      <VideoCameraAddOutlined />
      <WhatsAppOutlined />

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

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

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

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

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

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

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

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

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

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

      <Modal
        className={styles.detailDialog}
        width={'800px'}
        visible={itemDetailVisible}
        onCancel={() => setItemDetailVisible(false)}
        footer={null}
      >
        <h2 className='dialog-title' style={{textAlign: "center", fontSize: 19}}> 渠道码详情 </h2>
        <Row>
          <Col span={8} className={styles.leftPart}>
            <img src={currentItem.qr_code}/>
            <h3>{currentItem.name}</h3>
            <Button
              type={'primary'}
              onClick={() => {
                if (currentItem?.qr_code) {
                  FileSaver.saveAs(currentItem?.qr_code, `${currentItem.name}.png`);
                }
              }}
            >
              下载渠道码
            </Button>
            <Button
              onClick={() => {
                history.push(
                  `/staff-admin/customer-growth/contact-way/edit?id=${currentItem.id}`,
                );
              }}
            >
              修改
            </Button>
          </Col>
          <Col span={16} className={styles.rightPart}>
            <div className={styles.section}>
              <div className={styles.titleWrapper}>
                <div className={styles.divider}/>
                <span className={styles.title}>基本设置</span>
              </div>
              <div className={styles.formItem}>
                <span className={styles.title}>创建时间:</span>
                <span className='date'>
                  {moment(currentItem.created_at).format('YYYY-MM-DD HH:mm')}
                </span>
              </div>
              <div className={styles.formItem}>
                <span className={styles.title}>绑定员工:</span>
                {currentItem.staffs?.map((staff) => (
                  <Tag
                    key={staff.id}
                    className={styles.staffTag}
                    style={{opacity: staff.online === False ? '0.5' : '1'}}
                  >
                    <img className={styles.icon} src={staff.avatar_url} alt={staff.name}/>
                    <span className={styles.text}>{staff.name}</span>
                  </Tag>
                ))}
              </div>
              <div className={styles.formItem}>
                <span className={styles.title}>备份员工:</span>
                {currentItem.backup_staffs?.map((staff) => (
                  <Tag
                    key={staff.id}
                    className={styles.staffTag}
                    style={{opacity: staff.online === False ? '0.5' : '1'}}
                  >
                    <img className={styles.icon} src={staff.avatar_url} alt={staff.name}/>
                    <span className={styles.text}>{staff.name}</span>
                  </Tag>
                ))}
              </div>
              <p className={styles.formItem}>
                <span className={styles.title}>自动通过好友:</span>
                {currentItem.auto_skip_verify_enable === True && (
                  <span>
                    {currentItem.skip_verify_start_time && '~'}
                    {currentItem.skip_verify_end_time}自动通过
                  </span>
                )}
                {currentItem.auto_skip_verify_enable === False && <span>未开启</span>}
              </p>
              <p className={styles.formItem}>
                <span className={styles.title}>客户标签:</span>
                {currentItem.customer_tags?.map((tag) => (
                  <Tag key={tag.id} className={styles.staffTag}>
                    <span className={styles.text}>{tag.name}</span>
                  </Tag>
                ))}
              </p>
            </div>
          </Col>
        </Row>
      </Modal>
    </PageContainer>
  );
}
Example #9
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 #10
Source File: index.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
CustomerLossList: React.FC = () => {
  // const [currentItem, setCurrentItem] = useState<CustomerLossItem>({});
  // const [selectedItems, setSelectedItems] = useState<CustomerLossItem[]>([]);
  const [settingsModalVisible, setSettingsModalVisible] = useState<boolean>(false);
  const [exportLoading, setExportLoading] = useState<boolean>(false);
  const [settings, setSettings] = useState<CustomerLossNotifyRuleInterface>();
  const [extraFilterParams, setExtraFilterParams] = useState<any>();
  const [allStaffs, setAllStaffs] = useState<StaffOption[]>([]);
  const actionRef = useRef<ActionType>();
  const queryFormRef = useRef<FormInstance>();

  const formattedParams = (originParams: any) => {
    const params = {...originParams, ...extraFilterParams};
    if (params.customer_delete_staff_at) {
      [params.loss_start, params.loss_end] = params.customer_delete_staff_at;
      delete params.customer_delete_staff_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.ext_staff_id) {
      params.ext_staff_ids = params.ext_staff_id;
      delete params.ext_staff_id;
    }

    if (params.in_connection_time_range) {
      [params.time_span_lower_limit,params.time_span_upper_limit]=params.in_connection_time_range;
      delete params.in_connection_time_range;
    }

    return params;
  };

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

  useEffect(() => {
    GetCustomerLossNotifyRule()
      .then((resp) => {
        if (resp && resp.data) {
          const item = resp.data as CustomerLossNotifyRuleInterface;
          setSettings(item);
        }
      })
      .catch((err) => {
        message.error(err);
      });
  }, []);

  const columns: ProColumns<CustomerLossItem>[] = [
    {
      title: '流失客户',
      dataIndex: 'ext_customer_name',
      valueType: 'text',
      hideInSearch: true,
      render: (dom, item) => {
        return (
          <div className={'customer-info-field'}>
            <img
              src={item.customer_avatar}
              className={'icon'}
              alt={item.ext_customer_name}
            />
            <div className={'text-group'}>
              <p className={'text'}>
                {item.ext_customer_name}
              </p>
              {item.customer_corp_name && (
                <p className={'text'} style={{ color: '#eda150' }}>@{item.customer_corp_name}</p>
              )}
              {item.customer_type === 1 && (
                <p className={'text'} style={{ color: '#5ec75d' }}>@微信</p>
              )}
            </div>
          </div>
        );
      },
    },
    {
      title: '所属客服',
      dataIndex: 'ext_staff_id',
      valueType: 'text',
      renderFormItem: () => {
        return <StaffTreeSelect options={allStaffs}/>;
      },
      render: (dom, item) => {
        return (
          <Space>
            <div className={styles.staffTag}>
              <img src={item.staff_avatar} className={styles.icon} alt={item.staff_name}/>
              <span className={styles.text}>{item.staff_name}</span>
            </div>
          </Space>
        );
      },
    },
    {
      title: '客户标签',
      dataIndex: 'tags',
      valueType: 'text',
      hideInSearch: true,
      render: (dom, item) => {
        return <CollapsedTags limit={3} tags={item.tags}/>;
      },
    },

    {
      title: '流失时间',
      dataIndex: 'customer_delete_staff_at',
      valueType: 'dateRange',
      sorter: true,
      filtered: true,
      render: (dom, item) => {
        return (
          <div
            dangerouslySetInnerHTML={{
              __html: moment(item.customer_delete_staff_at)
                .format('YYYY-MM-DD HH:mm')
                .split(' ')
                .join('<br />'),
            }}
          />
        );
      },
    },
    {
      title: '添加时间',
      dataIndex: 'relation_create_at',
      valueType: 'dateRange',
      sorter: true,
      filtered: true,
      render: (dom, item) => {
        return (
          <div
            dangerouslySetInnerHTML={{
              __html: moment(item.relation_create_at)
                .format('YYYY-MM-DD HH:mm')
                .split(' ')
                .join('<br />'),
            }}
          />
        );
      },
    },
    {
      title: '添加时长',
      dataIndex: 'in_connection_time_range',
      valueType: 'text',
      sorter: true,
      renderFormItem: () => {
        return <NumberRangeInput formRef={queryFormRef} label={'天数'}/>;
      },
      render: (dom, item) => {
        return <span>{item.in_connection_time_range}天</span>;
      },
    },
    {
      title: '操作',
      width: 180,
      valueType: 'option',
      render: (__, 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: '客户流失',
        subTitle: (
          <a
            target={'_blank'}
            className={styles.tipsLink}
            // href={'https://www.openscrm.cn/wiki/contact-way'}
          >
            如何添加授权成员?
          </a>
        ),
      }}
      extra={[
        <Button
          key={'settings'}
          type='dashed'
          icon={<SettingOutlined style={{fontSize: 16, verticalAlign: '-3px'}}/>}
          onClick={() => {
            setSettingsModalVisible(true);
          }}
        >
          设置
        </Button>,

        <Button
          key={'export'}
          type='dashed'
          loading={exportLoading}
          icon={<CloudDownloadOutlined style={{fontSize: 16, verticalAlign: '-3px'}}/>}
          onClick={async () => {
            setExportLoading(true);
            try {
              const content = await ExportCustomerLoss(
                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>,
      ]}
    >
      <ProTable<CustomerLossItem>
        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,
            QueryCustomerLoss,
          );
        }}
        dateFormatter='string'
      />

      <ModalForm
        width={400}
        className={'dialog from-item-label-100w'}
        layout={'horizontal'}
        visible={settingsModalVisible}
        onVisibleChange={setSettingsModalVisible}
        onFinish={async (values) => {
          return await HandleRequest(
            {is_notify_staff: values.is_notify_staff ? True : False},
            UpdateCustomerLossNotifyRule,
            () => {
              actionRef.current?.clearSelected?.();
              actionRef.current?.reload?.();
            },
          );
        }}
      >
        <h2 className='dialog-title'> 流失提醒设置 </h2>
        <ProFormSwitch
          label={'客户流失提醒:'}
          checkedChildren='开启'
          unCheckedChildren='关闭'
          name='is_notify_staff'
          initialValue={settings?.is_notify_staff === True}
          tooltip={'开启后,当企业成员联系人被客户删除时,被删除的企业成员将收到一条消息提醒'}
        />
      </ModalForm>
    </PageContainer>
  );
}
Example #11
Source File: index.tsx    From dashboard with Apache License 2.0 4 votes vote down vote up
DeleteCustomerRecordList: React.FC = () => {
  const [settingsModalVisible, setSettingsModalVisible] = useState<boolean>(false);
  const [settings, setSettings] = useState<DeleteCustomerRecordNotifyRuleInterface>();
  const [allStaffs, setAllStaffs] = useState<StaffOption[]>([]);
  const actionRef = useRef<ActionType>();
  const formRef = useRef<FormInstance>();

  const [exportLoading, setExportLoading] = useState<boolean>(false);
  const queryFormRef = useRef<FormInstance>();
  const [extraFilterParams] = useState<any>();

  const formattedParams = (originParams: any) => {
    const params = {...originParams, ...extraFilterParams};
    if (params.relation_delete_at) {
      [params.delete_customer_start, params.delete_customer_end] = params.relation_delete_at;
      delete params.relation_delete_at;
    }
    if (params.relation_create_at) {
      [params.connection_create_start, params.connection_create_end] = params.relation_create_at;
      delete params.relation_create_at;
    }

    return params;
  };

  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(() => {
    GetDeleteCustomerRecordNotifyRule()
      .then((resp) => {
        if (resp && resp.data) {
          const item = resp.data as DeleteCustomerRecordNotifyRuleInterface;
          setSettings(item);
        }
      })
      .catch((err) => {
        message.error(err);
      });
  }, []);

  const columns: ProColumns<DeleteCustomerRecordItem>[] = [
    {
      title: 'ID',
      dataIndex: 'id',
      valueType: 'text',
      hideInTable: true,
      hideInSearch: true,
    },
    {
      title: '删除客户',
      dataIndex: 'ext_customer_name',
      valueType: 'text',
      hideInSearch: true,
      render: (dom, item) => {
        return (
          <div className={'customer-info-field'}>
            <img
              src={item.ext_customer_avatar}
              className={'icon'}
              alt={item.ext_customer_name}
            />
            <div className={'text-group'}>
              <p className={'text'}>
                {item.ext_customer_name}
              </p>
              {item.customer_corp_name && (
                <p className={'text'} style={{ color: '#eda150' }}>@{item.customer_corp_name}</p>
              )}
              {item.customer_type === 1 && (
                <p className={'text'} style={{ color: '#5ec75d' }}>@微信</p>
              )}
            </div>
          </div>
        );
      },
    },
    {
      title: '操作人',
      dataIndex: 'ext_staff_id',
      valueType: 'text',
      renderFormItem: () => {
        return <StaffTreeSelect options={allStaffs}/>;
      },
      render: (dom, item) => {
        return (
          <Space>
            <div className={'tag-like-staff-item'}>
              <img src={item.ext_staff_avatar} className={'icon'} alt={item.staff_name}/>
              <span className={'text'}>{item.staff_name}</span>
            </div>
          </Space>
        );
      },
    },
    {
      title: '删除时间',
      dataIndex: 'relation_delete_at',
      valueType: 'dateRange',
      sorter: true,
      filtered: true,
      render: (dom, item) => {
        return (
          <div
            dangerouslySetInnerHTML={{
              __html: moment(item.relation_delete_at)
                .format('YYYY-MM-DD HH:mm')
                .split(' ')
                .join('<br />'),
            }}
          />
        );
      },
    },
    {
      title: '添加时间',
      dataIndex: 'relation_create_at',
      valueType: 'dateRange',
      sorter: true,
      filtered: true,
      render: (dom, item) => {
        return (
          <div
            dangerouslySetInnerHTML={{
              __html: moment(item.relation_create_at)
                .format('YYYY-MM-DD HH:mm')
                .split(' ')
                .join('<br />'),
            }}
          />
        );
      },
    },

    {
      title: '操作',
      width: 180,
      valueType: 'option',
      render: (__,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: '删人提醒',
        subTitle: (
          <a
            target={'_blank'}
            className={styles.tipsLink}
            // href={'https://www.openscrm.cn/wiki/contact-way'}
          >
            什么是删人提醒?
          </a>
        ),
      }}
      extra={[
        <Button
          type="dashed"
          key={'settings'}
          icon={<SettingOutlined style={{fontSize: 16, verticalAlign: '-3px'}}/>}
          onClick={() => {
            setSettingsModalVisible(true);
          }}
        >
          设置
        </Button>,

        <Button
          key={'export'}
          type="dashed"
          loading={exportLoading}
          icon={<CloudDownloadOutlined style={{fontSize: 16, verticalAlign: '-3px'}}/>}
          onClick={async () => {
            setExportLoading(true);
            try {
              const content = await ExportDeleteCustomerRecord(
                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>,
      ]}
    >
      <ProTable<DeleteCustomerRecordItem>
        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) => {
          return ProTableRequestAdapter(
            formattedParams(originParams),
            sort,
            filter,
            QueryDeleteCustomerRecord,
          );
        }}
        dateFormatter="string"
      />

      <ModalForm
        formRef={formRef}
        width={660}
        className={'dialog from-item-label-100w'}
        layout={'horizontal'}
        visible={settingsModalVisible}
        onVisibleChange={setSettingsModalVisible}
        onFinish={async (values: DeleteCustomerRecordNotifyRuleInterface) => {
          console.log(values);
          return await HandleRequest(
            {
              is_notify_staff: values.is_notify_staff ? True : False,
              notify_type: values?.notify_type,
              ext_staff_ids: values?.ext_staff_ids,
            },
            UpdateDeleteCustomerRecordRule,
            () => {
              actionRef.current?.clearSelected?.();
              actionRef.current?.reload?.();
            },
          );
        }}
      >
        <h2 className="dialog-title"> 删人提醒设置 </h2>
        <Alert
          showIcon={true}
          closable={false}
          style={{marginBottom: 16}}
          type="info"
          message={
            <Text type={'secondary'}>
              开启「当前状态」之后,选择的「接收通知管理员」将会在企业微信中收到提醒通知
            </Text>
          }
        />
        <ProFormSwitch
          label={'当前状态:'}
          checkedChildren="开启"
          unCheckedChildren="关闭"
          name="is_notify_staff"
          initialValue={settings?.is_notify_staff === True}
        />

        <ProForm.Item
          label="接收通知管理员"
          name="ext_staff_ids"
          style={{width: 435}}
          initialValue={settings?.ext_staff_ids || []}
        >
          <StaffTreeSelect options={allStaffs}/>
        </ProForm.Item>

        <ProFormRadio.Group
          name="notify_type"
          label="通知频率"
          initialValue={settings?.notify_type}
          options={[
            {
              label: '每次通知',
              value: 1,
            },
            {
              label: '每天通知一次(每日8-9点推送昨日汇总)',
              value: 2,
            },
          ]}
        />
      </ModalForm>
    </PageContainer>
  );
}
Example #12
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 #13
Source File: VizOperationMenu.tsx    From datart with Apache License 2.0 4 votes vote down vote up
VizOperationMenu: FC<{
  onShareLinkClick?;
  onDownloadDataLinkClick?;
  onSaveAsVizs?;
  onReloadData?;
  onAddToDashBoard?;
  onPublish?;
  onRecycleViz?;
  allowDownload?: boolean;
  allowShare?: boolean;
  allowManage?: boolean;
  isArchived?: boolean;
}> = memo(
  ({
    onShareLinkClick,
    onDownloadDataLinkClick,
    onSaveAsVizs,
    onReloadData,
    onAddToDashBoard,
    onPublish,
    allowDownload,
    allowShare,
    allowManage,
    isArchived,
    onRecycleViz,
  }) => {
    const t = useI18NPrefix(`viz.action`);
    const tg = useI18NPrefix(`global`);

    const moreActionMenu = () => {
      const menus: any[] = [];

      if (onReloadData) {
        menus.push(
          <Menu.Item
            key="reloadData"
            icon={<ReloadOutlined />}
            onClick={onReloadData}
          >
            {t('syncData')}
          </Menu.Item>,
          <Menu.Divider key={'reloadDataLine'} />,
        );
      }

      if (allowManage && onSaveAsVizs) {
        menus.push(
          <Menu.Item key="saveAs" icon={<CopyFilled />} onClick={onSaveAsVizs}>
            {tg('button.saveAs')}
          </Menu.Item>,
        );
      }

      if (allowManage && onSaveAsVizs) {
        menus.push(
          <Menu.Item
            key="addToDash"
            icon={<FileAddOutlined />}
            onClick={() => onAddToDashBoard(true)}
          >
            {t('addToDash')}
          </Menu.Item>,
          <Menu.Divider key="addToDashLine" />,
        );
      }

      if (allowShare && onShareLinkClick) {
        menus.push(
          <Menu.Item
            key="shareLink"
            icon={<ShareAltOutlined />}
            onClick={onShareLinkClick}
          >
            {t('share.shareLink')}
          </Menu.Item>,
        );
      }

      if (allowDownload && onDownloadDataLinkClick) {
        menus.push(
          <Menu.Item key="downloadData" icon={<CloudDownloadOutlined />}>
            <Popconfirm
              placement="left"
              title={t('common.confirm')}
              onConfirm={() => {
                onDownloadDataLinkClick(DownloadFileType.Excel);
              }}
              okText={t('common.ok')}
              cancelText={t('common.cancel')}
            >
              {t('share.downloadData')}
            </Popconfirm>
          </Menu.Item>,
          <Menu.Item key="downloadPDF" icon={<CloudDownloadOutlined />}>
            <Popconfirm
              placement="left"
              title={t('common.confirm')}
              onConfirm={() => {
                onDownloadDataLinkClick(DownloadFileType.Pdf);
              }}
              okText={t('common.ok')}
              cancelText={t('common.cancel')}
            >
              {t('share.downloadPDF')}
            </Popconfirm>
          </Menu.Item>,
          <Menu.Item key="downloadPicture" icon={<CloudDownloadOutlined />}>
            <Popconfirm
              placement="left"
              title={t('common.confirm')}
              onConfirm={() => {
                onDownloadDataLinkClick(DownloadFileType.Image);
              }}
              okText={t('common.ok')}
              cancelText={t('common.cancel')}
            >
              {t('share.downloadPicture')}
            </Popconfirm>
          </Menu.Item>,
          <Menu.Divider />,
          <Menu.Divider key="downloadDataLine" />,
        );
      }

      if (allowManage && !isArchived && onPublish) {
        menus.push(
          <Menu.Item
            key="publish"
            icon={<VerticalAlignBottomOutlined />}
            onClick={onPublish}
          >
            {t('unpublish')}
          </Menu.Item>,
        );
      }

      if (allowManage && onRecycleViz) {
        menus.push(
          <Menu.Item key="delete" icon={<DeleteOutlined />}>
            <Popconfirm
              title={tg('operation.archiveConfirm')}
              onConfirm={onRecycleViz}
            >
              {tg('button.archive')}
            </Popconfirm>
          </Menu.Item>,
        );
      }

      return <Menu>{menus}</Menu>;
    };

    return <StyleVizOperationMenu>{moreActionMenu()}</StyleVizOperationMenu>;
  },
)
Example #14
Source File: BoardDropdownList.tsx    From datart with Apache License 2.0 4 votes vote down vote up
BoardDropdownList: FC<Props> = memo(
  ({ onOpenShareLink, openStoryList }) => {
    const t = useI18NPrefix(`viz.action`);
    const tg = useI18NPrefix(`global`);
    const dispatch = useDispatch();
    const {
      allowDownload,
      allowShare,
      allowManage,
      renderMode,
      status,
      orgId,
      boardId,
    } = useContext(BoardContext);
    const recycleViz = useRecycleViz(orgId, boardId, 'DASHBOARD');
    const saveAsViz = useSaveAsViz();
    const reloadData = () => {
      dispatch(widgetsQueryAction({ boardId, renderMode }));
    };
    const { onBoardToDownLoad } = useContext(BoardActionContext);
    const { publishBoard } = usePublishBoard(boardId, 'DASHBOARD', status);
    return (
      <Menu>
        <Menu.Item
          key="reloadData"
          onClick={reloadData}
          icon={<ReloadOutlined />}
        >
          {t('syncData')}
        </Menu.Item>
        {allowShare && (
          <>
            <Menu.Divider key={'shareLinkLine'} />
            <Menu.Item
              key={'shareLink'}
              onClick={onOpenShareLink}
              icon={<ShareAltOutlined />}
            >
              {t('share.shareLink')}
            </Menu.Item>
          </>
        )}
        {allowDownload && (
          <>
            <Menu.Divider key={'downloadDataLine'} />
            <Menu.Item key={'downloadData'} icon={<CloudDownloadOutlined />}>
              <Popconfirm
                placement="left"
                title={t('common.confirm')}
                okText={t('common.ok')}
                cancelText={t('common.cancel')}
                onConfirm={() => {
                  onBoardToDownLoad?.(DownloadFileType.Excel);
                }}
              >
                {t('share.downloadData')}
              </Popconfirm>
            </Menu.Item>
            <Menu.Item key={'downloadPDF'} icon={<CloudDownloadOutlined />}>
              <Popconfirm
                placement="left"
                title={t('common.confirm')}
                okText={t('common.ok')}
                cancelText={t('common.cancel')}
                onConfirm={() => {
                  onBoardToDownLoad?.(DownloadFileType.Pdf);
                }}
              >
                {t('share.downloadPDF')}
              </Popconfirm>
            </Menu.Item>
            <Menu.Item key={'downloadPicture'} icon={<CloudDownloadOutlined />}>
              <Popconfirm
                placement="left"
                title={t('common.confirm')}
                okText={t('common.ok')}
                cancelText={t('common.cancel')}
                onConfirm={() => {
                  onBoardToDownLoad?.(DownloadFileType.Image);
                }}
              >
                {t('share.downloadPicture')}
              </Popconfirm>
            </Menu.Item>
          </>
        )}
        {allowManage && (
          <>
            <Menu.Divider key="unpublishLine" />
            {status === 2 && (
              <Menu.Item
                key={'unpublish'}
                onClick={publishBoard}
                icon={<FileAddOutlined />}
              >
                {t('unpublish')}
              </Menu.Item>
            )}

            <Menu.Item
              key={'saveAs'}
              onClick={() => saveAsViz(boardId, 'DASHBOARD')}
              icon={<CopyFilled />}
            >
              {tg('button.saveAs')}
            </Menu.Item>
            <Menu.Item
              key={'addToStory'}
              onClick={openStoryList}
              icon={<FileAddOutlined />}
            >
              {t('addToStory')}
            </Menu.Item>
            <Menu.Item
              key={'archive'}
              onClick={recycleViz}
              icon={<DeleteOutlined />}
            >
              {tg('button.archive')}
            </Menu.Item>
          </>
        )}
      </Menu>
    );
  },
)