lodash#uniqWith TypeScript Examples

The following examples show how to use lodash#uniqWith. 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: contractWrappers.ts    From webapp with MIT License 6 votes vote down vote up
getHistoricBalances = async (
  positions: ProtectedLiquidity[],
  blockNow: number,
  pools: MinimalPool[]
): Promise<PoolHistoricBalance[][]> => {
  const timeScales: TimeScale[] = (
    [
      [1, "day"],
      [7, "week"]
    ] as [number, string][]
  ).map(([days, label]) => ({
    blockHeight: rewindBlocksByDays(blockNow, days),
    days,
    label
  }));
  const uniqueAnchors = uniqWith(
    positions.map(pos => pos.poolToken),
    compareString
  );
  const relevantPools = pools.filter(pool =>
    uniqueAnchors.some(anchor => compareString(pool.anchorAddress, anchor))
  );
  // @ts-ignore
  return fetchHistoricBalances(timeScales, relevantPools);
}
Example #2
Source File: observables.ts    From webapp with MIT License 6 votes vote down vote up
uniquePoolReserves = (
  positions: ProtectedLiquidity[]
): { poolToken: string; reserveToken: string }[] =>
  uniqWith(
    positions,
    (a, b) =>
      compareString(a.poolToken, b.poolToken) &&
      compareString(a.reserveToken, b.reserveToken)
  ).map(position => ({
    reserveToken: position.reserveToken,
    poolToken: position.poolToken
  }))
Example #3
Source File: eosBancor.ts    From webapp with MIT License 6 votes vote down vote up
@action async onAuthChange(currentUser: string | false) {
    if (currentUser) {
      Sentry.setUser({ id: currentUser });
      const reserves = uniqWith(
        this.relaysList.flatMap(relay => relay.reserves),
        (a, b) => compareString(a.id, b.id)
      );
      this.fetchTokenBalancesIfPossible(reserves);
    } else {
      Sentry.configureScope(scope => scope.setUser(null));
    }
  }
Example #4
Source File: eosBancor.ts    From webapp with MIT License 6 votes vote down vote up
@action async fetchTokenBalancesIfPossible(tokens: TokenBalanceParam[]) {
    if (!this.currentUser) return;
    const tokensFetched = this.currentUserBalances;
    const allTokens = _.uniqWith(
      this.relaysList.flatMap(relay => relay.reserves),
      (a, b) => compareString(a.id, b.id)
    );
    const tokenBalancesNotYetFetched = _.differenceWith(
      allTokens,
      tokensFetched,
      compareAgnosticToBalanceParam
    );

    const tokensToAskFor = _.uniqWith(
      [
        ...tokens,
        ...tokenBalancesNotYetFetched.map(agnosticToTokenBalanceParam)
      ],
      compareToken
    );

    return vxm.eosNetwork.getBalances({ tokens: tokensToAskFor, slow: false });
  }
Example #5
Source File: eosBancor.ts    From webapp with MIT License 6 votes vote down vote up
@action async fetchBalancesFromReserves(relays: DryRelay[]) {
    const tokens = relays
      .flatMap(relay => relay.reserves)
      .map(reserve => ({
        contract: reserve.contract,
        symbol: reserve.symbol.code().to_string()
      }));

    const uniqueTokens = _.uniqWith(
      tokens,
      (a, b) =>
        compareString(a.symbol, b.symbol) &&
        compareString(a.contract, b.contract)
    );

    return vxm.eosNetwork.getBalances({
      tokens: uniqueTokens,
      slow: false
    });
  }
Example #6
Source File: explorerService.ts    From nautilus-wallet with MIT License 6 votes vote down vote up
public async getTokenRates(): Promise<AssetPriceRate> {
    const fromDate = new Date();
    fromDate.setDate(fromDate.getDate() - 30);

    const { data } = await axios.get<ErgoDexPool[]>(`https://api.ergodex.io/v1/amm/markets`, {
      params: {
        from: this.getUtcTimestamp(fromDate),
        to: this.getUtcTimestamp(new Date())
      }
    });

    const filtered = uniqWith(
      data.filter((x) => x.baseId === ERG_TOKEN_ID),
      (a, b) =>
        a.quoteId === b.quoteId && new BigNumber(a.baseVolume.value).isLessThan(b.baseVolume.value)
    );

    return asDict(
      filtered.map((r) => {
        return {
          [r.quoteId]: {
            erg: new BigNumber(1).dividedBy(r.lastPrice).toNumber()
          }
        };
      })
    );
  }
Example #7
Source File: observables.ts    From webapp with MIT License 5 votes vote down vote up
getTokenMeta = async (currentNetwork: EthNetworks) => {
  const networkVars = getNetworkVariables(currentNetwork);
  if (currentNetwork == EthNetworks.Ropsten) {
    return [
      {
        symbol: "BNT",
        contract: networkVars.bntToken,
        precision: 18
      },
      {
        symbol: "DAI",
        contract: "0xc2118d4d90b274016cb7a54c03ef52e6c537d957",
        precision: 18
      },
      {
        symbol: "WBTC",
        contract: "0xbde8bb00a7ef67007a96945b3a3621177b615c44",
        precision: 8
      },
      {
        symbol: "BAT",
        contract: "0x443fd8d5766169416ae42b8e050fe9422f628419",
        precision: 18
      },
      {
        symbol: "LINK",
        contract: "0x20fe562d797a42dcb3399062ae9546cd06f63280",
        precision: 18
      },
      {
        contract: "0x4F5e60A76530ac44e0A318cbc9760A2587c34Da6",
        symbol: "YYYY"
      },
      {
        contract: "0x63B75DfA4E87d3B949e876dF2Cd2e656Ec963466",
        symbol: "YYY"
      },
      {
        contract: "0xAa2A908Ca3E38ECEfdbf8a14A3bbE7F2cA2a1BE4",
        symbol: "XXX"
      },
      {
        contract: "0xe4158797A5D87FB3080846e019b9Efc4353F58cC",
        symbol: "XXX"
      }
    ].map(
      (x): TokenMeta => ({
        ...x,
        id: x.contract,
        image: defaultImage,
        name: x.symbol
      })
    );
  }
  if (currentNetwork !== EthNetworks.Mainnet)
    throw new Error("Ropsten and Mainnet supported only.");

  const res: AxiosResponse<TokenMeta[]> = await axios.get(
    tokenMetaDataEndpoint
  );

  const drafted = res.data
    .filter(({ symbol, contract, image }) =>
      [symbol, contract, image].every(Boolean)
    )
    .map(x => ({ ...x, id: x.contract }));

  const existingEth = drafted.find(x => compareString(x.symbol, "eth"))!;

  const withoutEth = drafted.filter(meta => !compareString(meta.symbol, "eth"));
  const addedEth = {
    ...existingEth,
    id: ethReserveAddress,
    contract: ethReserveAddress
  };
  const final = [addedEth, existingEth, ...withoutEth];
  return uniqWith(final, (a, b) => compareString(a.id, b.id));
}
Example #8
Source File: eosBancor.ts    From webapp with MIT License 5 votes vote down vote up
@action async init(param?: ModuleParam) {
    this.pullEvents();

    if (this.initialised) return this.refresh();

    try {
      const [usdPriceOfBnt, v2Relays, tokenMeta] = await Promise.all([
        vxm.bancor.fetchUsdPriceOfBnt(),
        fetchMultiRelays(),
        getTokenMeta()
      ]);
      this.setTokenMeta(tokenMeta);
      this.setBntPrice(usdPriceOfBnt);

      const v1Relays = getHardCodedRelays();
      const allDry = [...v1Relays, ...v2Relays.map(multiToDry)].filter(
        noBlackListedReservesDry(blackListedTokens)
      );

      this.fetchTokenBalancesIfPossible(
        _.uniqWith(
          allDry.flatMap(x =>
            x.reserves.map(x => ({ ...x, symbol: x.symbol.code().to_string() }))
          ),
          compareToken
        )
      );

      const quickTrade =
        param &&
        param.tradeQuery &&
        param.tradeQuery.base &&
        param.tradeQuery.quote;
      if (quickTrade) {
        const { base: fromId, quote: toId } = param!.tradeQuery!;
        await this.bareMinimumForTrade({
          fromId,
          toId,
          v1Relays,
          v2Relays,
          tokenMeta
        });
      } else {
        await this.addPools({
          multiRelays: v2Relays,
          dryDelays: v1Relays,
          tokenMeta
        });
      }

      this.setInitialised(true);
      this.setLoadingPools(false);
    } catch (e) {
      throw new Error(`Threw inside eosBancor: ${e.message}`);
    }
  }
Example #9
Source File: eosBancor.ts    From webapp with MIT License 5 votes vote down vote up
@mutation updateRelayFeed(feeds: RelayFeed[]) {
    this.relayFeed = _.uniqWith(
      [...feeds, ...this.relayFeed],
      (a, b) =>
        compareString(a.smartTokenId, b.smartTokenId) &&
        compareString(a.tokenId, b.tokenId)
    );
  }
Example #10
Source File: eosBancor.ts    From webapp with MIT License 5 votes vote down vote up
@mutation updateMultiRelays(relays: EosMultiRelay[]) {
    const meshedRelays = _.uniqWith(
      [...relays, ...this.relaysList],
      compareEosMultiRelay
    );
    this.relaysList = meshedRelays;
  }
Example #11
Source File: loadAccounts.ts    From metaplex with Apache License 2.0 5 votes vote down vote up
loadAccounts = async (connection: Connection) => {
  const state: MetaState = getEmptyMetaState();
  const updateState = makeSetter(state);
  const forEachAccount = processingAccounts(updateState);

  const forEach =
    (fn: ProcessAccountsFunc) => async (accounts: AccountAndPubkey[]) => {
      for (const account of accounts) {
        await fn(account, updateState);
      }
    };

  const loadVaults = () =>
    getProgramAccounts(connection, VAULT_ID).then(
      forEachAccount(processVaultData),
    );
  const loadAuctions = () =>
    getProgramAccounts(connection, AUCTION_ID).then(
      forEachAccount(processAuctions),
    );
  const loadMetaplex = () =>
    getProgramAccounts(connection, METAPLEX_ID).then(
      forEachAccount(processMetaplexAccounts),
    );
  const loadCreators = () =>
    getProgramAccounts(connection, METAPLEX_ID, {
      filters: [
        {
          dataSize: MAX_WHITELISTED_CREATOR_SIZE,
        },
      ],
    }).then(forEach(processMetaplexAccounts));
  const loadMetadata = () =>
    pullMetadataByCreators(connection, state, updateState);
  const loadEditions = () =>
    pullEditions(connection, updateState, state, state.metadata);

  const loading = [
    loadCreators().then(loadMetadata).then(loadEditions),
    loadVaults(),
    loadAuctions(),
    loadMetaplex(),
  ];

  await Promise.all(loading);

  state.metadata = uniqWith(
    state.metadata,
    (a: ParsedAccount<Metadata>, b: ParsedAccount<Metadata>) =>
      a.pubkey === b.pubkey,
  );

  return state;
}
Example #12
Source File: RenameProvider.ts    From language-tools with MIT License 5 votes vote down vote up
function unique<T>(array: T[]): T[] {
    return uniqWith(array, isEqual);
}
Example #13
Source File: get-graphql-input-type.ts    From prisma-nestjs-graphql with MIT License 5 votes vote down vote up
/**
 * Find input type for graphql field decorator.
 */
export function getGraphqlInputType(
  inputTypes: DMMF.SchemaArgInputType[],
  pattern?: string,
) {
  let result: DMMF.SchemaArgInputType | undefined;

  inputTypes = inputTypes.filter(t => !['null', 'Null'].includes(String(t.type)));
  inputTypes = uniqWith(inputTypes, isEqual);

  if (inputTypes.length === 1) {
    return inputTypes[0];
  }

  const countTypes = countBy(inputTypes, x => x.location);
  const isOneType = Object.keys(countTypes).length === 1;

  if (isOneType) {
    result = inputTypes.find(x => x.isList);
    if (result) {
      return result;
    }
  }

  if (pattern) {
    if (pattern.startsWith('matcher:') || pattern.startsWith('match:')) {
      const { 1: patternValue } = pattern.split(':', 2);
      const isMatch = outmatch(patternValue, { separator: false });
      result = inputTypes.find(x => isMatch(String(x.type)));
      if (result) {
        return result;
      }
    }
    result = inputTypes.find(x => String(x.type).includes(pattern));
    if (result) {
      return result;
    }
  }

  result = inputTypes.find(x => x.location === 'inputObjectTypes');
  if (result) {
    return result;
  }

  if (
    countTypes.enumTypes &&
    countTypes.scalar &&
    inputTypes.some(x => x.type === 'Json' && x.location === 'scalar')
  ) {
    result = inputTypes.find(x => x.type === 'Json' && x.location === 'scalar');
    if (result) {
      return result;
    }
  }

  throw new TypeError(
    // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
    `Cannot get matching input type from ${
      inputTypes.map(x => x.type).join(', ') || 'zero length inputTypes'
    }`,
  );
}
Example #14
Source File: build-gird-hierarchy.ts    From S2 with MIT License 4 votes vote down vote up
buildGridHierarchy = (params: GridHeaderParams) => {
  const {
    addTotalMeasureInTotal,
    addMeasureInTotalQuery,
    parentNode,
    currentField,
    fields,
    facetCfg,
    hierarchy,
  } = params;

  const index = fields.indexOf(currentField);

  const { dataSet, values, spreadsheet } = facetCfg;
  const fieldValues: FieldValue[] = [];

  let query = {};
  if (parentNode.isTotals) {
    // add total measures
    if (addTotalMeasureInTotal) {
      query = getDimsCondition(parentNode.parent, true);
      // add total measures
      fieldValues.push(...values.map((v) => new TotalMeasure(v)));
    }
  } else {
    // field(dimension)'s all values
    query = getDimsCondition(parentNode, true);

    const dimValues = dataSet.getDimensionValues(currentField, query);

    const arrangedValues = layoutArrange(
      dimValues,
      facetCfg,
      parentNode,
      currentField,
    );
    fieldValues.push(...(arrangedValues || []));

    // add skeleton for empty data

    const fieldName = dataSet.getFieldName(currentField);

    if (isEmpty(fieldValues)) {
      if (currentField === EXTRA_FIELD) {
        fieldValues.push(...dataSet.fields?.values);
      } else {
        fieldValues.push(fieldName);
      }
    }
    // hide measure in columns
    hideMeasureColumn(fieldValues, currentField, facetCfg);
    // add totals if needed
    addTotals({
      currentField,
      lastField: fields[index - 1],
      isFirstField: index === 0,
      fieldValues,
      spreadsheet,
    });
  }

  const hiddenColumnsDetail = spreadsheet.store.get('hiddenColumnsDetail');
  const isEqualValueLeafNode =
    uniqWith(spreadsheet.getColumnLeafNodes(), (prev, next) => {
      return prev.value === next.value;
    }).length === 1;

  const displayFieldValues = fieldValues.filter((value) => {
    // 去除多余的节点
    if (isUndefined(value)) {
      return false;
    }

    if (isEmpty(hiddenColumnsDetail)) {
      return true;
    }

    return hiddenColumnsDetail.every((detail) => {
      return detail.hideColumnNodes.every((node) => {
        // 1. 有数值字段 (hideMeasureColumn: false) 隐藏父节点
        // 2. 多列头场景(数值挂列头, 隐藏数值列头, 自定义目录多指标等) 叶子节点是数值, 叶子节点的文本内容都一样, 需要额外比较父级节点的id是否相同, 确定到底隐藏哪一列
        // 3. 自定义虚拟指标列 (列头内容未知)
        const isMeasureField = node.field === EXTRA_FIELD;
        const isCustomColumnField = node.field === EXTRA_COLUMN_FIELD;
        if (isMeasureField || isCustomColumnField || isEqualValueLeafNode) {
          return (
            node.parent.id !== parentNode.id && node.parent.value !== value
          );
        }
        // 没有数值字段 (hideMeasureColumn: true) 隐藏自己即可
        return node.value !== value;
      });
    });
  });

  generateHeaderNodes({
    currentField,
    fields,
    fieldValues: displayFieldValues,
    facetCfg,
    hierarchy,
    parentNode,
    level: index,
    query,
    addMeasureInTotalQuery,
    addTotalMeasureInTotal,
  });
}
Example #15
Source File: api-view.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
ApiView = ({ dataSource, onChangeVersion, deprecated, specProtocol }: IProps) => {
  const params = routeInfoStore.useStore((s) => s.params);
  const [hasAccess, accessDetail] = apiMarketStore.useStore((s) => [
    s.assetVersionDetail.hasAccess,
    s.assetVersionDetail.access,
  ]);
  const [{ apiData, currentApi, testModalVisible, authModal, authed }, updater, update] = useUpdate<IState>({
    apiData: {},
    currentApi: '',
    testModalVisible: false,
    authModal: false,
    authed: false,
  });
  React.useEffect(() => {
    if (dataSource.spec) {
      SwaggerParser.dereference(
        cloneDeep(dataSource.spec),
        {},
        (err: Error | null, data: OpenAPI.Document | undefined) => {
          if (err) {
            message.error(i18n.t('default:failed to parse API description document'));
            throw err;
          }
          updater.apiData((data || {}) as ApiData);
        },
      );
    }
    return () => {
      updater.apiData({} as ApiData);
    };
  }, [dataSource.spec, updater]);
  const getAuthInfo = React.useCallback(() => {
    const authInfo = JSON.parse(sessionStorage.getItem(`asset-${params.assetID}`) || '{}');
    return authInfo[`version-${params.versionID}`];
  }, [params.assetID, params.versionID]);
  React.useEffect(() => {
    const authInfo = getAuthInfo();
    let isAuthed = !isEmpty(authInfo);
    if (isAuthed && accessDetail.authentication === authenticationMap['sign-auth'].value) {
      isAuthed = !!authInfo.clientSecret;
    }
    updater.authed(isAuthed);
  }, [getAuthInfo, updater, accessDetail.authentication]);
  React.useEffect(() => {
    return () => {
      sessionStorage.removeItem(`asset-${params.assetID}`);
    };
  }, [params.assetID]);
  const fullingUrl = React.useCallback(
    (path: string) => {
      let url = path;
      if (apiData.basePath && !['', '/'].includes(apiData.basePath)) {
        url = apiData.basePath + path;
      }
      return url;
    },
    [apiData.basePath],
  );
  const [tagMap, apiMap] = React.useMemo(() => {
    const _tagMap = {} as TagMap;
    const _apiMap = {} as { [K: string]: ApiMapItem };
    if (isEmpty(apiData.paths)) {
      return [{}, {}];
    }
    map(apiData.paths, (methodMap, path) => {
      const _path = fullingUrl(path);
      const httpRequests = pick(methodMap, HTTP_METHODS);
      const restParams = omit(methodMap, HTTP_METHODS);
      map(httpRequests, (api, method) => {
        const parameters = uniqWith(
          [...(api.parameters || [])].concat(restParams.parameters || []),
          (a, b) => a.in === b.in && a.name === b.name,
        );
        const item: ApiMapItem = {
          _method: method,
          _path,
          ...api,
          ...restParams,
          parameters,
        };
        map(api.tags || ['OTHER'], (tagName) => {
          if (_tagMap[tagName]) {
            _tagMap[tagName].push(item);
          } else {
            _tagMap[tagName] = [];
            _tagMap[tagName].push(item);
          }
        });
        _apiMap[method + _path] = item;
      });
    });
    return [_tagMap, _apiMap];
  }, [apiData.paths, fullingUrl]);

  const handleChange = React.useCallback(
    (key: string) => {
      updater.currentApi(key);
    },
    [updater],
  );
  const handleShowTest = () => {
    if (!authed) {
      message.error(i18n.t('please authenticate first'));
      return;
    }
    updater.testModalVisible(true);
  };
  const handleOk = (data: AutoInfo) => {
    const authInfo = JSON.parse(sessionStorage.getItem(`asset-${params.assetID}`) || '{}');
    authInfo[`version-${params.versionID}`] = data;
    sessionStorage.setItem(`asset-${params.assetID}`, JSON.stringify(authInfo));
    update({
      authed: true,
      authModal: false,
    });
  };

  const testButton = hasAccess ? (
    <>
      <Button onClick={handleShowTest}>{i18n.t('test')}</Button>
      {
        <Button
          className="ml-2"
          onClick={() => {
            updater.authModal(true);
          }}
        >
          {authed ? i18n.t('recertification') : i18n.t('authentication')}
        </Button>
      }
    </>
  ) : null;
  const fieldsList: IFormItem[] = [
    {
      label: 'clientID',
      name: 'clientID',
    },
    ...insertWhen(accessDetail.authentication === authenticationMap['sign-auth'].value, [
      {
        label: 'clientSecret',
        name: 'clientSecret',
        getComp: () => <Input.Password />,
      },
    ]),
  ];
  const currentApiSource = apiMap[currentApi] || {};
  const parametersMap: Dictionary<any[]> = groupBy(currentApiSource.parameters, 'in');
  if (specProtocol && specProtocol.includes('oas3')) {
    Object.assign(parametersMap, convertToOpenApi2(currentApiSource));
  }
  const autoInfo = getAuthInfo();
  return (
    <div className="apis-view flex justify-between items-center flex-1">
      <div className="apis-view-left">
        <ApiMenu list={tagMap} onChange={handleChange} onChangeVersion={onChangeVersion} />
      </div>
      <div className="apis-view-right">
        {deprecated ? (
          <ErdaAlert className="mb-4" type="warning" message={i18n.t('the current version is deprecated')} />
        ) : null}
        <ApiDetail key={currentApi} dataSource={currentApiSource} extra={testButton} specProtocol={specProtocol} />
      </div>
      <TestModal
        key={`${currentApiSource._method}${currentApiSource._path}`}
        visible={testModalVisible}
        onCancel={() => {
          updater.testModalVisible(false);
        }}
        dataSource={{
          autoInfo,
          basePath: apiData.basePath,
          url: currentApiSource._path,
          method: currentApiSource._method?.toUpperCase(),
          requestScheme: parametersMap,
          host: apiData.host,
          protocol: apiData.schemes?.includes('https') ? 'https' : 'http',
        }}
      />
      <FormModal
        title={i18n.t('authentication')}
        visible={authModal}
        fieldsList={fieldsList}
        onCancel={() => {
          updater.authModal(false);
        }}
        formData={getAuthInfo()}
        onOk={handleOk}
      />
    </div>
  );
}
Example #16
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>
  );
}