lodash#intersection TypeScript Examples

The following examples show how to use lodash#intersection. 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: index.ts    From EIP-Bot with Creative Commons Zero v1.0 Universal 6 votes vote down vote up
innerJoinAncestors = (
  parent: TestResults,
  objects: TestResults[]
) => {
  const objectPaths = objects.map(getAllTruthyObjectPaths);
  const commonPaths = intersection(...objectPaths);
  const clearPaths = getAllTruthyObjectPaths(parent).filter(
    (path) => !commonPaths.includes(path)
  );

  return clearPaths.reduce(
    (obj, path) => set(obj, path, undefined),
    parent
  ) as TestResults;
}
Example #2
Source File: multisig.ts    From subscan-multisig-react with Apache License 2.0 6 votes vote down vote up
export function useUnapprovedAccounts() {
  const { accounts } = useApi();
  const { multisigAccount } = useMultisig();
  const getUnapprovedInjectedList = useCallback(
    (data: Entry | null) => {
      if (!data) {
        return [];
      }

      const extensionAddresses = accounts?.map((item) => item.address) || [];
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const multisigPairAddresses = (multisigAccount?.meta.addressPair as any[])?.map((item) => item.address);
      const extensionInPairs = intersection(extensionAddresses, multisigPairAddresses);
      const approvedExtensionAddresses = intersection(extensionInPairs, data.approvals);
      return difference(extensionInPairs, approvedExtensionAddresses);
    },
    [accounts, multisigAccount?.meta.addressPair]
  );

  return [getUnapprovedInjectedList];
}
Example #3
Source File: selectors.ts    From jellyfin-audio-player with MIT License 6 votes vote down vote up
selectDownloadedTracks = (trackIds: EntityId[]) => (
    createSelector(
        selectAllDownloads,
        ({ entities, ids }) => {
            return intersection(trackIds, ids)
                .filter((id) => entities[id]?.isComplete);
        }
    )
)
Example #4
Source File: route-selector.component.ts    From fyle-mobile-app with MIT License 6 votes vote down vote up
onTxnFieldsChange() {
    const keyToControlMap: { [id: string]: AbstractControl } = {
      distance: this.form.controls.distance,
    };

    for (const control of Object.values(keyToControlMap)) {
      control.clearValidators();
      control.updateValueAndValidity();
    }

    for (const txnFieldKey of intersection(['distance'], Object.keys(this.txnFields))) {
      const control = keyToControlMap[txnFieldKey];

      if (this.txnFields[txnFieldKey].is_mandatory) {
        if (txnFieldKey === 'distance') {
          control.setValidators(
            this.isConnected ? Validators.compose([Validators.required, this.customDistanceValidator]) : null
          );
        }
      }
      control.updateValueAndValidity();
    }

    this.form.updateValueAndValidity();
  }
Example #5
Source File: offline.service.ts    From fyle-mobile-app with MIT License 6 votes vote down vote up
getProjectCount(params: { categoryIds: string[] } = { categoryIds: [] }) {
    return this.getProjects().pipe(
      map((projects) => {
        const filterdProjects = projects.filter((project) => {
          if (params.categoryIds.length) {
            return intersection(params.categoryIds, project.org_category_ids).length > 0;
          } else {
            return true;
          }
        });
        return filterdProjects.length;
      })
    );
  }
Example #6
Source File: perm-export.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
checkValidRole = (data: Obj, role: Obj) => {
  const roleKeys = map(role, 'value');
  const newData = cloneDeep(data);

  const check = (_d: Obj) => {
    // eslint-disable-next-line no-param-reassign
    if (_d.role) _d.role = intersection(_d.role, roleKeys);
    map(_d, (item) => {
      typeof item === 'object' && check(item);
    });
  };
  check(newData);
  return newData;
}
Example #7
Source File: text-edit.tsx    From erda-ui with GNU Affero General Public License v3.0 6 votes vote down vote up
validateSameKey = (val: string, fullConfigData: PIPELINE_CONFIG.ConfigItem[] = []) => {
  if (!val) return i18n.t('can not be empty');
  const valData: PIPELINE_CONFIG.ConfigItem[] = JSON.parse(val);
  const keys = valData.map((item) => item.key);
  const otherKeys = fullConfigData.filter((item) => item.type !== ConfigTypeMap.kv.key).map((item) => item.key);
  if (uniq(keys).length !== keys.length) {
    return i18n.t('the same {key} exists', { key: 'key' });
  } else if (intersection(keys, otherKeys).length) {
    return i18n.t('{name} already exists in {place}', {
      name: intersection(keys, otherKeys).join(','),
      place: i18n.t('common:Other Type'),
    });
  }
  return '';
}
Example #8
Source File: validate.ts    From backend with MIT License 6 votes vote down vote up
async function ensureNeverPreviouslyMatchedTogether(coach: Student, coachee: Pupil, manager: EntityManager) {
    const projectMatchesCoach = await coach.projectMatches;
    const projectMatchesCoachee = await coachee.projectMatches;

    //reload the project match from database, to ensure they really have the pupil and student relationships loaded (they are eager and not always loaded automatically)
    const reloadProjectMatches = async pm => reloadProjectMatchesInstances(pm, manager);

    const partnersCoach = (await reloadProjectMatches(projectMatchesCoach)).map(m => m.pupil.wix_id);
    const partnersCoachee = (await reloadProjectMatches(projectMatchesCoachee)).map(m => m.student.wix_id);

    return intersection([...partnersCoach, ...partnersCoachee], [coach.wix_id, coachee.wix_id]).length === 0;
}
Example #9
Source File: validate.ts    From backend with MIT License 6 votes vote down vote up
async function ensureNeverPreviouslyMatchedTogether(tutor: Student, tutee: Pupil, manager: EntityManager) {
    const matchesTutor = await tutor.matches;
    const matchesTutee = await tutee.matches;

    //reload the match from database, to ensure they really have the pupil and student relationships loaded (they are eager and not always loaded automatically)
    const reloadMatches = async m => reloadMatchesInstances(m, manager);

    const partnersTutor = (await reloadMatches(matchesTutor)).map(m => m.pupil.wix_id);
    const partnersTutee = (await reloadMatches(matchesTutee)).map(m => m.student.wix_id);

    return intersection([...partnersTutor, ...partnersTutee], [tutor.wix_id, tutee.wix_id]).length === 0;
}
Example #10
Source File: custom-tree-pivot-data-set.ts    From S2 with MIT License 5 votes vote down vote up
processDataCfg(dataCfg: S2DataConfig): S2DataConfig {
    // 自定义行头有如下几个特点
    // 1、rows配置必须是空,需要额外添加 $$extra$$ 定位数据(标记指标的id)
    // 2、要有配置 fields.rowCustomTree(行头结构)
    // 3、values 不需要参与计算,默认就在行头结构中
    dataCfg.fields.rows = [EXTRA_FIELD];
    dataCfg.fields.valueInCols = false;
    const { data, ...restCfg } = dataCfg;
    const { values } = dataCfg.fields;
    // 将源数据中的value值,映射为 $$extra$$,$$value$$
    // {
    // province: '四川',    province: '四川',
    // city: '成都',   =>   city: '成都',
    // price='11'           price='11'
    //                      $$extra$$=price
    //                      $$value$$=11
    // 此时 province, city 均配置在columns里面
    // }
    const transformedData = [];
    forEach(data, (dataItem) => {
      if (isEmpty(intersection(keys(dataItem), values))) {
        transformedData.push(dataItem);
      } else {
        forEach(values, (value) => {
          if (has(dataItem, value)) {
            transformedData.push({
              ...dataItem,
              [EXTRA_FIELD]: value,
              [VALUE_FIELD]: dataItem[value],
            });
          }
        });
      }
    });

    return {
      data: uniq(transformedData),
      ...restCfg,
    };
  }
Example #11
Source File: smoke.test.ts    From analytics-next with MIT License 5 votes vote down vote up
function compareSchema(results: RemovePromise<ReturnType<typeof run>>) {
  const classicReqs = results.classic.networkRequests
    .filter((n) => n.url.includes('api.segment') && !n.url.endsWith('/m'))
    .sort()

  const nextReqs = results.next.networkRequests
    .filter((n) => n.url.includes('api.segment') && !n.url.endsWith('/m'))
    .sort()

  nextReqs.forEach((req, index) => {
    const classic = classicReqs[index]
    if (!classic) {
      return
    }

    expect(req.url).toEqual(classic.url)

    // @ts-ignore need all sources to be rebuilt first
    if (classic.data?._metadata) {
      // @ts-ignore need all sources to be rebuilt first
      delete classic.data._metadata.bundledIds

      // @ts-ignore sort unbundled metadata because of a breaking change in the SegmentIO destination
      classic.data._metadata.unbundled = uniq(
        // @ts-ignore
        classic.data._metadata.unbundled.sort()
      )
    }

    expect(req.data).toContainSchema(classic.data)

    const nextSchema = objectSchema(req.data as object)
    const classicSchema = objectSchema(classic.data as object)

    const intersectionKeys = without(
      intersection(nextSchema, classicSchema),

      // These are different on purpose
      'context.library.name',
      'context.library.version',

      // We use the same website with a slightly different URL
      'context.page.search',
      'properties.search',
      'context.page.url',
      'properties.url',

      'messageId',
      'anonymousId',

      // We do an integrations specific check below since 'next'
      // may have action-destinations that 'classic' does not
      'integrations'
    )

    expect((req.data as Record<string, JSONValue>).integrations).toEqual(
      expect.objectContaining(
        (classic.data as Record<string, JSONValue>).integrations
      )
    )

    const flatNext = flat(req.data) as Record<string, JSONValue>
    const flatClassic = flat(classic.data) as Record<string, JSONValue>

    intersectionKeys.forEach((key) => {
      const comparison = {
        url: req.url,
        key,
        next: flatNext[key],
        classic: flatClassic[key],
      }

      expect({ ...comparison, val: comparison.next }).toEqual({
        ...comparison,
        val: comparison.classic,
      })
    })
  })
}
Example #12
Source File: handlers.ts    From leda with MIT License 5 votes vote down vote up
createClickHandler = (props: ButtonProps) => (ev: React.MouseEvent<HTMLButtonElement>): void => {
  const {
    onClick, onValidationFail, isDisabled, isLoading, scrollDelay,
    scrollOffset, form: formProp, shouldScrollToInvalidFields,
  } = props;

  if (isDisabled || isLoading) return; // если кнопка отключена или в состояниии загрузки, прервать выполнение функции

  // если кнопка участвует в валидации форм
  if (formProp) {
    const buttonFormNames = Array.isArray(formProp) ? formProp : [formProp];
    const formNames = getForms().map((form) => form.name);
    const validButtonFormNames = intersection(formNames, buttonFormNames);

    const isEachFormValid = validButtonFormNames
      .map((currentForm) => validate(currentForm)) // validate all forms
      .every((item) => item); // tell me if all of them are valid

    if (!isEachFormValid) {
      const invalidForms = getForms()
        .filter((currentForm) => validButtonFormNames.includes(currentForm.name))
        .filter((currentForm) => currentForm.fields.some((field) => !field.isValid));

      if (isFunction(onValidationFail)) {
        onValidationFail({ ...ev, invalidForms });
      }

      if (shouldScrollToInvalidFields && invalidForms.length > 0) {
        const firstInvalidFormName = invalidForms[0].name;
        // data-form для buttonGroup, использовать просто [from=""] нельзя, т.к. захватит кнопки тоже
        const formElements = document.querySelectorAll(`input[form="${firstInvalidFormName}"], [data-form="${firstInvalidFormName}"`);
        // ждем пока обновятся данные
        setTimeout(() => {
          const invalidElement = Array.from(formElements).find((element) => element.getAttribute('aria-invalid') === 'true');

          if (invalidElement) {
            const invalidElementRect = invalidElement.getBoundingClientRect();
            const isIE = !!(document as any).documentMode || /Edge/.test(navigator.userAgent);
            const offset = invalidElementRect.top - (scrollOffset ?? 0);
            if (isIE) {
              window.scrollBy(0, offset);
            } else {
              window.scrollBy({
                top: offset,
                behavior: 'smooth',
              });
            }
          }
        }, scrollDelay ?? 0);
      }

      return;
    }
  }

  if (isFunction(onClick)) {
    if (!formProp) {
      onClick(ev);
      return;
    }

    const forms = getForms(formProp);
    const formsObject = fromFormArraytoFormObject(forms);

    const customEvent = {
      ...ev,
      forms,
      form: formsObject,
    };

    onClick(customEvent);
  }
}
Example #13
Source File: compose.ts    From gant-design with MIT License 5 votes vote down vote up
findDependencies = (
    changedValueObject: object,
    { ...schema }: Schema,
    mapSubSchema: mapSubSchema,
    form: WrappedFormUtils
): void => {
    // 改变的key
    // object.product
    const changeKeys = objectToPath(changedValueObject)
    if (!changeKeys.length) return
    const dependenciesChangeValue = {}
    function setFieldsValue(data) {
        for (const key of Reflect.ownKeys(data)) {
            Reflect.set(dependenciesChangeValue, key, data[key])
        }
    }

    /**进入schema子树去寻找依赖项 */
    function inner(schemaKey: string[], subSchema): Array<Promise<Change>> {
        /**将所有变更推送到同一次更新流程、而不再时单独去在整个树上更新、防止出现一次更新流程中,修改了多个子树,导致前后更新不一致的问题 */
        const changedSchema = []
        const { dependencies = [], onDependenciesChange, type, ...restSchema } = subSchema
        if (type !== Types.object) {
            if (get(dependencies, 'length') && get(intersection(dependencies, changeKeys), 'length') && onDependenciesChange) {
                const dependenciesValues = dependencies.map(deKey => {
                    if (changeKeys.includes(deKey)) return get(changedValueObject, deKey)
                    return form.getFieldValue(deKey)
                })
                const mergeSchema = onDependenciesChange(dependenciesValues, cloneDeep(restSchema), { ...form, setFieldsValue })
                changedSchema.push(
                    Promise.resolve(mergeSchema).then(
                        newSubSchema => ({ key: schemaKey, schema: { ...subSchema, ...newSubSchema } })
                    )
                )
            }
        } else if (subSchema.propertyType) {
            subSchema.propertyType = { ...subSchema.propertyType }
            const entries = Object.entries(subSchema.propertyType)
            for (const [subSchemaKey, schemaValue] of entries) {
                const { dependencies = [], onDependenciesChange, type } = schemaValue as any
                if (
                    // 找到了依赖项或者是object,进入递归
                    (get(dependencies, 'length') && get(intersection(dependencies, changeKeys), 'length') && onDependenciesChange) ||
                    type === Types.object
                ) {
                    const subChangedSchema = inner([...schemaKey, subSchemaKey], schemaValue)
                    changedSchema.push(...subChangedSchema)
                }
            }
        }
        return changedSchema
    }

    const allChange = inner([], schema)
    Promise.all(allChange).then(changes => {
        mapSubSchema(schema, changes, () => {
            if (!isEmpty(dependenciesChangeValue)) {
                form.setFieldsValue(dependenciesChangeValue)
            }
        })
    })
}
Example #14
Source File: AuthHelper.ts    From node-experience with MIT License 5 votes vote down vote up
static validatePermissions(permissions: string[]): void
    {
        if (!isEmpty(permissions) && isEmpty(intersection(permissions, Permissions.permissions())))
        {
            throw new WrongPermissionsException();
        }
    }
Example #15
Source File: with-auth.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
getAuth = (permObj: IPermObj, checkRole: string[]) => {
  if (permObj && permObj.pass) return permObj.pass;
  if (permObj && checkRole) {
    const l = intersection(permObj.role, checkRole);
    if (l.length) return true;
  }
  return false;
}
Example #16
Source File: org.tsx    From erda-ui with GNU Affero General Public License v3.0 5 votes vote down vote up
setLocationByAuth = (authObj: { roles: string[]; orgName: string }) => {
  const curPathname = location.pathname;
  const { roles, orgName } = authObj;
  const checkMap = {
    dataEngineer: {
      isCurPage: curPathname.startsWith(`/${orgName}/fdp`),
      authRole: intersection(orgPerm.entryFastData.role, roles),
    },
    orgCenter: {
      isCurPage: curPathname.startsWith(`/${orgName}/orgCenter`),
      authRole: intersection(orgPerm.entryOrgCenter.role, roles),
    },
    msp: {
      isCurPage: curPathname.startsWith(`/${orgName}/msp`),
      authRole: intersection(orgPerm.entryMsp.role, roles),
    },
    ecp: {
      isCurPage: curPathname.startsWith(`/${orgName}/ecp`),
      authRole: intersection(orgPerm.ecp.view.role, roles),
    },
    cmp: {
      isCurPage: curPathname.startsWith(`/${orgName}/cmp`),
      authRole: intersection(orgPerm.cmp.showApp.role, roles),
    },
    dop: {
      isCurPage: curPathname.startsWith(`/${orgName}/dop`),
      authRole: intersection(orgPerm.dop.read.role, roles),
    },
  };

  map(checkMap, (item) => {
    // 当前页,但是无权限,则重置
    if (item.isCurPage && !item.authRole.length) {
      let resetPath = goTo.resolve.orgRoot({ orgName });
      if (roles.toString() === 'DataEngineer') {
        // DataEngineer redirect to DataEngineer role page
        resetPath = `/${orgName}/fdp/__cluster__/__workspace__/data-govern-platform/data-source`;
      } else if (roles.toString() === 'Ops') {
        // 企业运维只有云管的权限
        resetPath = `/${orgName}/cmp/overview`;
      } else if (roles.toString() === 'EdgeOps') {
        // 边缘运维工程师只有边缘计算平台的权限
        resetPath = `/${orgName}/ecp/application`;
      }
      goTo(resetPath);
    }
  });
}
Example #17
Source File: BrickTree.tsx    From next-basics with GNU General Public License v3.0 4 votes vote down vote up
export function BrickTree(props: BrickTreeProps): React.ReactElement {
  const {
    selectedKeys: _selectedKeys,
    checkedKeys: _checkedKeys,
    expandedKeys: _expandedKeys,
    configProps = {},
    dataSource = [],
    searchable = false,
    searchParent = false,
    placeholder = "",
    checkAllEnabled,
    checkedFilterConfig: { field, value, operator } = {},
    suffixBrick,
    showSpecificationTitleStyle,
    defaultExpandAll,
    deselectable,
    alsoSearchByKey,
  } = props;
  const [allChecked, setAllChecked] = useState(false);
  const [indeterminate, setIndeterminate] = useState(false);
  const [selectedKeys, setSelectedKeys] = useState<React.Key[]>();
  const [filterCheckedKeys, setFilterCheckedKeys] = useState<React.Key[]>();
  const [checkedKeys, setCheckedKeys] = useState<
    React.Key[] | { checked: React.Key[]; halfChecked: React.Key[] }
  >();
  const [expandedKeys, setExpandedKeys] = useState<React.Key[]>();
  const [searchValue, setSearchValue] = useState<string>();
  const treeContainerRef = useRef<HTMLDivElement>();
  const nodeMatchedRef = useRef<boolean>(false);

  const treeData = useMemo(() => getTreeNodes(dataSource), [dataSource]);
  const filterTreeKeys = useMemo(
    () =>
      flat(dataSource)
        .filter((v) => compareFunMap[operator]?.(value, get(v, field)))
        .map((v) => v.key) || [],
    [dataSource]
  );

  useEffect(() => {
    setSelectedKeys(_selectedKeys);
  }, [_selectedKeys]);
  // istanbul ignore next
  useEffect(() => {
    setCheckedKeys(_checkedKeys);
    setFilterCheckedKeys(difference(_checkedKeys, filterTreeKeys));
    if (Array.isArray(_checkedKeys)) {
      if (
        _checkedKeys.length === 0 ||
        intersection(_checkedKeys, getAllKeys(treeData))?.length === 0
      ) {
        setAllChecked(false);
        setIndeterminate(false);
      } else {
        const checkedKeySet = new Set(_checkedKeys);
        const allChecked = getAllCheckedState(treeData, checkedKeySet);
        setAllChecked(allChecked);
        setIndeterminate(!allChecked);
      }
    }
  }, [_checkedKeys]);
  useEffect(() => {
    setExpandedKeys(_expandedKeys);
  }, [_expandedKeys]);
  useEffect(() => {
    nodeMatchedRef.current = false;
  }, [searchValue]);

  let searchValueLength: number;

  if (searchValue) {
    searchValueLength = searchValue.length;
  }

  const onChange = useCallback(
    debounce((value: string) => {
      // 等到 expandedKeys 更新后,也就是展开状态改变后,再触发跳转到第一个匹配项
      setTimeout(() => {
        setSearchValue(value);
      });

      if (value) {
        const expandedKeys: React.Key[] = [];

        getExpandedKeysBySearchValue(
          treeData,
          value.toLocaleLowerCase(),
          expandedKeys,
          {
            searchParent,
            alsoSearchByKey,
          }
        );
        setExpandedKeys(expandedKeys);
      }
    }, 500),
    [treeData, searchParent, alsoSearchByKey]
  );

  const onCheckAllChange = (e: CheckboxChangeEvent) => {
    const checked = e.target.checked;
    const checkedKeys = checked ? getAllKeys(treeData) : [];
    let _filterCheckedKeys: React.Key[] = [];
    if (props.checkedFilterConfig) {
      _filterCheckedKeys = difference(checkedKeys, filterTreeKeys);
      setFilterCheckedKeys(_filterCheckedKeys);
    }
    setAllChecked(checked);
    setIndeterminate(false);
    setCheckedKeys(checkedKeys);
    props.onCheck?.(
      props.checkedFilterConfig ? _filterCheckedKeys : checkedKeys
    );
  };

  const onSelect = (
    selectedKeys: React.Key[],
    info: {
      event: "select";
      selected: boolean;
      node: EventDataNode;
      selectedNodes: DataNode[];
      nativeEvent: MouseEvent;
    }
  ) => {
    if (!deselectable && selectedKeys.length === 0) {
      return;
    }

    setSelectedKeys(selectedKeys);
    props.onSelect?.(selectedKeys, info);
  };

  const onCheck = (
    checkedKeys:
      | React.Key[]
      | { checked: React.Key[]; halfChecked: React.Key[] }
  ) => {
    let _filterCheckedKeys: React.Key[] = [];
    if (props.checkedFilterConfig) {
      _filterCheckedKeys = difference(
        checkedKeys as React.Key[],
        filterTreeKeys
      );
      Array.isArray(checkedKeys) && setFilterCheckedKeys(_filterCheckedKeys);
    }
    setCheckedKeys(checkedKeys);

    if (Array.isArray(checkedKeys)) {
      if (checkedKeys.length === 0) {
        setAllChecked(false);
        setIndeterminate(false);
      } else {
        const checkedKeySet = new Set(checkedKeys);
        const allChecked = treeData.every((node) =>
          checkedKeySet.has(node.key)
        );

        setAllChecked(allChecked);
        setIndeterminate(allChecked ? false : true);
      }
    }
    props.onCheck?.(
      props.checkedFilterConfig ? _filterCheckedKeys : checkedKeys
    );
  };

  const onExpand = (expandedKeys: React.Key[]) => {
    setExpandedKeys(expandedKeys);
  };

  return (
    <>
      {searchable && (
        <Input.Search
          placeholder={placeholder}
          onChange={(e) => onChange(e.target.value)}
          style={{ marginBottom: 8 }}
          data-testid="search-input"
        />
      )}
      {configProps.checkable && checkAllEnabled && (
        <div style={{ marginBottom: 6, display: "flex", alignItems: "center" }}>
          <Checkbox
            checked={allChecked}
            indeterminate={indeterminate}
            onChange={onCheckAllChange}
            data-testid="check-all-checkbox"
          >
            {i18n.t(`${NS_PRESENTATIONAL_BRICKS}:${K.SELECT_ALL}`)}
          </Checkbox>
          <span style={{ marginLeft: "auto" }} className="checkedNum">
            {i18n.t(`${NS_PRESENTATIONAL_BRICKS}:${K.SELECTED_OPTIONS}`, {
              number: props.checkedFilterConfig
                ? filterCheckedKeys?.length
                : (Array.isArray(checkedKeys) && checkedKeys?.length) || 0,
            })}
          </span>
        </div>
      )}
      <div
        className={classNames(styles.treeWrapper, {
          [styles.withSuffix]: !isEmpty(suffixBrick?.useBrick),
          [styles.titleSpace]: showSpecificationTitleStyle,
        })}
        ref={treeContainerRef}
      >
        {treeData?.length ? (
          <Tree
            {...configProps}
            treeData={treeData}
            titleRender={(node) => {
              const { title: _title, children, key: _key } = node;
              let title: React.ReactNode = _title;
              //根据ui规范,全部或者默认的节点,字体加粗,间距加宽
              const allOrDefaultFlag =
                (title === "全部" || title === "默认") && !children;
              if (
                typeof _title === "string" &&
                searchValue &&
                (searchParent ? true : !children?.length)
              ) {
                const lowerCaseSearchValue = searchValue.toLocaleLowerCase();
                const index = _title
                  .toLocaleLowerCase()
                  .indexOf(lowerCaseSearchValue);
                const kIndex = alsoSearchByKey
                  ? _key
                      .toString()
                      ?.toLocaleLowerCase()
                      .indexOf(lowerCaseSearchValue)
                  : -1;

                if (index >= 0) {
                  const beforeStr = _title.substring(0, index);
                  const matchStr = _title.substring(
                    index,
                    searchValueLength + index
                  );
                  const afterStr = _title.substring(searchValueLength + index);

                  title = (
                    <span
                      ref={(() => {
                        if (!nodeMatchedRef.current) {
                          nodeMatchedRef.current = true;

                          return (el: HTMLElement) => {
                            if (el) {
                              const nodeEl =
                                el.closest(".ant-tree-treenode") || el;
                              const treeContainerEl = treeContainerRef.current;

                              treeContainerEl.scrollBy(
                                undefined,
                                nodeEl.getBoundingClientRect().top -
                                  treeContainerEl.getBoundingClientRect().top
                              );
                            }
                          };
                        } else {
                          return null;
                        }
                      })()}
                    >
                      {alsoSearchByKey ? (
                        // 如果也按key搜索,就整体高亮(因为key不会展示)
                        <span className={styles.matchTextTotal}>{_title}</span>
                      ) : (
                        <>
                          {beforeStr}
                          <span className={styles.matchText}>{matchStr}</span>
                          {afterStr}
                        </>
                      )}
                    </span>
                  );
                } else if (kIndex >= 0) {
                  title = (
                    <span
                      ref={(() => {
                        if (!nodeMatchedRef.current) {
                          nodeMatchedRef.current = true;

                          return (el: HTMLElement) => {
                            if (el) {
                              const nodeEl =
                                el.closest(".ant-tree-treenode") || el;
                              const treeContainerEl = treeContainerRef.current;

                              treeContainerEl.scrollBy(
                                undefined,
                                nodeEl.getBoundingClientRect().top -
                                  treeContainerEl.getBoundingClientRect().top
                              );
                            }
                          };
                        } else {
                          return null;
                        }
                      })()}
                    >
                      <span className={styles.matchTextTotal}>{_title}</span>
                    </span>
                  );
                }
              }

              if (!isEmpty(suffixBrick?.useBrick)) {
                return (
                  <div className={styles.suffixBrickWrapper}>
                    <span
                      className={
                        showSpecificationTitleStyle && allOrDefaultFlag
                          ? styles.allOrDefault
                          : null
                      }
                    >
                      {title}
                    </span>
                    <BrickAsComponent
                      useBrick={suffixBrick.useBrick}
                      data={node}
                    />
                  </div>
                );
              }

              return showSpecificationTitleStyle && allOrDefaultFlag ? (
                <span className={styles.allOrDefault}>{title}</span>
              ) : (
                title
              );
            }}
            selectedKeys={selectedKeys}
            checkedKeys={checkedKeys}
            {...(expandedKeys ? { expandedKeys: expandedKeys } : {})}
            defaultExpandAll={defaultExpandAll}
            onSelect={onSelect}
            onCheck={onCheck}
            onExpand={onExpand}
          />
        ) : (
          <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
        )}
      </div>
    </>
  );
}
Example #18
Source File: add-edit-mileage.page.ts    From fyle-mobile-app with MIT License 4 votes vote down vote up
ionViewWillEnter() {
    from(this.tokenService.getClusterDomain()).subscribe((clusterDomain) => {
      this.clusterDomain = clusterDomain;
    });

    this.navigateBack = this.activatedRoute.snapshot.params.navigate_back;
    this.expenseStartTime = new Date().getTime();
    this.fg = this.fb.group({
      mileage_vehicle_type: [],
      dateOfSpend: [, this.customDateValidator],
      route: [],
      paymentMode: [, Validators.required],
      purpose: [],
      project: [],
      billable: [],
      sub_category: [, Validators.required],
      custom_inputs: new FormArray([]),
      costCenter: [],
      add_to_new_report: [],
      report: [],
      duplicate_detection_reason: [],
    });

    const today = new Date();
    this.maxDate = moment(this.dateService.addDaysToDate(today, 1)).format('y-MM-D');

    this.setupDuplicateDetection();

    this.fg.reset();
    this.title = 'Add Mileage';

    this.activeIndex = this.activatedRoute.snapshot.params.activeIndex;
    this.reviewList =
      this.activatedRoute.snapshot.params.txnIds && JSON.parse(this.activatedRoute.snapshot.params.txnIds);

    this.title =
      this.activeIndex > -1 && this.reviewList && this.activeIndex < this.reviewList.length ? 'Review' : 'Edit';
    if (this.activatedRoute.snapshot.params.id) {
      this.mode = 'edit';
    }

    this.isExpandedView = this.mode !== 'add';

    const orgSettings$ = this.offlineService.getOrgSettings();
    const orgUserSettings$ = this.offlineService.getOrgUserSettings();

    this.isAdvancesEnabled$ = orgSettings$.pipe(
      map(
        (orgSettings) =>
          (orgSettings.advances && orgSettings.advances.enabled) ||
          (orgSettings.advance_requests && orgSettings.advance_requests.enabled)
      )
    );

    this.setupNetworkWatcher();

    this.recentlyUsedValues$ = this.isConnected$.pipe(
      take(1),
      switchMap((isConnected) => {
        if (isConnected) {
          return this.recentlyUsedItemsService.getRecentlyUsed();
        } else {
          return of(null);
        }
      })
    );

    this.recentlyUsedMileageLocations$ = this.recentlyUsedValues$.pipe(
      map((recentlyUsedValues) => ({
        recent_start_locations: recentlyUsedValues?.recent_start_locations || [],
        recent_end_locations: recentlyUsedValues?.recent_end_locations || [],
        recent_locations: recentlyUsedValues?.recent_locations || [],
      }))
    );

    this.txnFields$ = this.getTransactionFields();
    this.paymentModes$ = this.getPaymentModes();
    this.homeCurrency$ = this.offlineService.getHomeCurrency();
    this.subCategories$ = this.getSubCategories();
    this.setupFilteredCategories(this.subCategories$);
    this.projectCategoryIds$ = this.getProjectCategoryIds();
    this.isProjectVisible$ = this.projectCategoryIds$.pipe(
      switchMap((projectCategoryIds) => this.offlineService.getProjectCount({ categoryIds: projectCategoryIds }))
    );
    this.comments$ = this.statusService.find('transactions', this.activatedRoute.snapshot.params.id);

    this.filteredCategories$.subscribe((subCategories) => {
      if (subCategories.length) {
        this.fg.controls.sub_category.setValidators(Validators.required);
      } else {
        this.fg.controls.sub_category.clearValidators();
      }
      this.fg.controls.sub_category.updateValueAndValidity();
    });

    this.mileageConfig$ = this.getMileageConfig();

    this.etxn$ = iif(() => this.mode === 'add', this.getNewExpense(), this.getEditExpense());

    this.setupTfcDefaultValues();

    this.isAmountDisabled$ = this.etxn$.pipe(map((etxn) => !!etxn.tx.admin_amount));

    this.isIndividualProjectsEnabled$ = orgSettings$.pipe(
      map((orgSettings) => orgSettings.advanced_projects && orgSettings.advanced_projects.enable_individual_projects)
    );

    this.individualProjectIds$ = orgUserSettings$.pipe(
      map((orgUserSettings: any) => orgUserSettings.project_ids || [])
    );

    this.isProjectsEnabled$ = orgSettings$.pipe(
      map((orgSettings) => orgSettings.projects && orgSettings.projects.enabled)
    );

    this.customInputs$ = this.getCustomInputs();

    this.isCostCentersEnabled$ = orgSettings$.pipe(map((orgSettings) => orgSettings.cost_centers.enabled));

    this.costCenters$ = forkJoin({
      orgSettings: orgSettings$,
      orgUserSettings: orgUserSettings$,
    }).pipe(
      switchMap(({ orgSettings, orgUserSettings }) => {
        if (orgSettings.cost_centers.enabled) {
          return this.offlineService.getAllowedCostCenters(orgUserSettings);
        } else {
          return of([]);
        }
      }),
      map((costCenters) =>
        costCenters.map((costCenter) => ({
          label: costCenter.name,
          value: costCenter,
        }))
      )
    );

    this.recentlyUsedCostCenters$ = forkJoin({
      costCenters: this.costCenters$,
      recentValue: this.recentlyUsedValues$,
    }).pipe(
      concatMap(({ costCenters, recentValue }) =>
        this.recentlyUsedItemsService.getRecentCostCenters(costCenters, recentValue)
      )
    );

    this.reports$ = this.reportService
      .getFilteredPendingReports({ state: 'edit' })
      .pipe(map((reports) => reports.map((report) => ({ label: report.rp.purpose, value: report }))));

    this.txnFields$
      .pipe(
        distinctUntilChanged((a, b) => isEqual(a, b)),
        switchMap((txnFields) =>
          forkJoin({
            isConnected: this.isConnected$.pipe(take(1)),
            orgSettings: this.offlineService.getOrgSettings(),
            costCenters: this.costCenters$,
            isIndividualProjectsEnabled: this.isIndividualProjectsEnabled$,
            individualProjectIds: this.individualProjectIds$,
          }).pipe(
            map(({ isConnected, orgSettings, costCenters, isIndividualProjectsEnabled, individualProjectIds }) => ({
              isConnected,
              txnFields,
              orgSettings,
              costCenters,
              isIndividualProjectsEnabled,
              individualProjectIds,
            }))
          )
        )
      )
      .subscribe(
        ({ isConnected, txnFields, costCenters, orgSettings, individualProjectIds, isIndividualProjectsEnabled }) => {
          const keyToControlMap: { [id: string]: AbstractControl } = {
            purpose: this.fg.controls.purpose,
            cost_center_id: this.fg.controls.costCenter,
            txn_dt: this.fg.controls.dateOfSpend,
            project_id: this.fg.controls.project,
            billable: this.fg.controls.billable,
          };

          for (const control of Object.values(keyToControlMap)) {
            control.clearValidators();
            control.updateValueAndValidity();
          }

          for (const txnFieldKey of intersection(Object.keys(keyToControlMap), Object.keys(txnFields))) {
            const control = keyToControlMap[txnFieldKey];
            if (txnFields[txnFieldKey].is_mandatory) {
              if (txnFieldKey === 'txn_dt') {
                control.setValidators(
                  isConnected ? Validators.compose([Validators.required, this.customDateValidator]) : null
                );
              } else if (txnFieldKey === 'cost_center_id') {
                control.setValidators(
                  isConnected && costCenters && costCenters.length > 0 ? Validators.required : null
                );
              } else if (txnFieldKey === 'project_id') {
                control.setValidators(
                  orgSettings.projects.enabled && isIndividualProjectsEnabled && individualProjectIds.length === 0
                    ? null
                    : Validators.required
                );
              } else {
                control.setValidators(isConnected ? Validators.required : null);
              }
            }
            control.updateValueAndValidity();
          }

          this.fg.updateValueAndValidity();
        }
      );

    this.isAmountCapped$ = this.etxn$.pipe(
      map((etxn) => isNumber(etxn.tx.admin_amount) || isNumber(etxn.tx.policy_amount))
    );

    this.isAmountDisabled$ = this.etxn$.pipe(map((etxn) => !!etxn.tx.admin_amount));

    this.isCriticalPolicyViolated$ = this.etxn$.pipe(
      map((etxn) => isNumber(etxn.tx.policy_amount) && etxn.tx.policy_amount < 0.0001)
    );

    this.getPolicyDetails();

    this.isBalanceAvailableInAnyAdvanceAccount$ = this.fg.controls.paymentMode.valueChanges.pipe(
      switchMap((paymentMode) => {
        if (paymentMode && paymentMode.acc && paymentMode.acc.type === 'PERSONAL_ACCOUNT') {
          return this.offlineService
            .getAccounts()
            .pipe(
              map(
                (accounts) =>
                  accounts.filter(
                    (account) =>
                      account &&
                      account.acc &&
                      account.acc.type === 'PERSONAL_ADVANCE_ACCOUNT' &&
                      account.acc.tentative_balance_amount > 0
                  ).length > 0
              )
            );
        }
        return of(false);
      })
    );

    this.rate$ = iif(
      () => this.mode === 'edit',
      // this.etxn$.pipe(
      //   map(etxn => etxn.tx.mileage_rate)
      // )
      this.fg.valueChanges.pipe(
        map((formValue) => formValue.mileage_vehicle_type),
        switchMap((vehicleType) =>
          forkJoin({
            orgSettings: this.offlineService.getOrgSettings(),
            etxn: this.etxn$,
          }).pipe(
            map(({ orgSettings, etxn }) => {
              if (etxn.tx.mileage_rate && etxn.tx.mileage_vehicle_type === vehicleType) {
                return etxn.tx.mileage_rate;
              } else {
                return orgSettings.mileage[vehicleType];
              }
            })
          )
        ),
        shareReplay(1)
      ),
      this.fg.valueChanges.pipe(
        map((formValue) => formValue.mileage_vehicle_type),
        switchMap((vehicleType) =>
          this.offlineService.getOrgSettings().pipe(map((orgSettings) => orgSettings.mileage[vehicleType]))
        ),
        shareReplay(1)
      )
    );

    this.amount$ = combineLatest(this.fg.valueChanges, this.rate$).pipe(
      map(([formValue, mileageRate]) => {
        const distance = formValue.route?.distance || 0;
        return distance * mileageRate;
      }),
      shareReplay(1)
    );

    const selectedProject$ = this.etxn$.pipe(
      switchMap((etxn) => {
        if (etxn.tx.project_id) {
          return of(etxn.tx.project_id);
        } else {
          return forkJoin({
            orgSettings: this.offlineService.getOrgSettings(),
            orgUserSettings: this.offlineService.getOrgUserSettings(),
          }).pipe(
            map(({ orgSettings, orgUserSettings }) => {
              if (orgSettings.projects.enabled) {
                return orgUserSettings && orgUserSettings.preferences && orgUserSettings.preferences.default_project_id;
              }
            })
          );
        }
      }),
      switchMap((projectId) => {
        if (projectId) {
          return this.projectService.getbyId(projectId);
        } else {
          return of(null);
        }
      })
    );

    const selectedPaymentMode$ = this.etxn$.pipe(
      switchMap((etxn) =>
        iif(
          () => etxn.tx.source_account_id,
          this.paymentModes$.pipe(
            map((paymentModes) =>
              paymentModes
                .map((res) => res.value)
                .find((paymentMode) => {
                  if (paymentMode.acc.displayName === 'Personal Card/Cash') {
                    return paymentMode.acc.id === etxn.tx.source_account_id && !etxn.tx.skip_reimbursement;
                  } else {
                    return paymentMode.acc.id === etxn.tx.source_account_id;
                  }
                })
            )
          ),
          of(null)
        )
      )
    );

    const defaultPaymentMode$ = this.paymentModes$.pipe(
      map((paymentModes) =>
        paymentModes.map((res) => res.value).find((paymentMode) => paymentMode.acc.displayName === 'Personal Card/Cash')
      )
    );

    this.recentlyUsedProjects$ = forkJoin({
      recentValues: this.recentlyUsedValues$,
      mileageCategoryIds: this.projectCategoryIds$,
      eou: this.authService.getEou(),
    }).pipe(
      switchMap(({ recentValues, mileageCategoryIds, eou }) =>
        this.recentlyUsedItemsService.getRecentlyUsedProjects({
          recentValues,
          eou,
          categoryIds: mileageCategoryIds,
        })
      )
    );

    const selectedSubCategory$ = this.etxn$.pipe(
      switchMap((etxn) =>
        iif(
          () => etxn.tx.org_category_id,
          this.offlineService
            .getAllEnabledCategories()
            .pipe(
              map((subCategories) =>
                subCategories
                  .filter((subCategory) => subCategory.sub_category?.toLowerCase() !== subCategory?.name.toLowerCase())
                  .find((subCategory) => subCategory?.id === etxn.tx.org_category_id)
              )
            ),
          of(null)
        )
      )
    );

    const selectedReport$ = this.etxn$.pipe(
      switchMap((etxn) =>
        iif(
          () => etxn.tx.report_id,
          this.reports$.pipe(
            map((reportOptions) =>
              reportOptions.map((res) => res.value).find((reportOption) => reportOption.rp.id === etxn.tx.report_id)
            )
          ),
          of(null)
        )
      )
    );

    const selectedCostCenter$ = this.etxn$.pipe(
      switchMap((etxn) => {
        if (etxn.tx.cost_center_id) {
          return of(etxn.tx.cost_center_id);
        } else {
          return forkJoin({
            orgSettings: this.offlineService.getOrgSettings(),
            costCenters: this.costCenters$,
          }).pipe(
            map(({ orgSettings, costCenters }) => {
              if (orgSettings.cost_centers.enabled) {
                if (costCenters.length === 1 && this.mode === 'add') {
                  return costCenters[0].value.id;
                }
              }
            })
          );
        }
      }),
      switchMap((costCenterId) => {
        if (costCenterId) {
          return this.costCenters$.pipe(
            map((costCenters) =>
              costCenters.map((res) => res.value).find((costCenter) => costCenter.id === costCenterId)
            )
          );
        } else {
          return of(null);
        }
      })
    );

    const selectedCustomInputs$ = this.etxn$.pipe(
      switchMap((etxn) =>
        this.offlineService
          .getCustomInputs()
          .pipe(
            map((customFields) =>
              this.customFieldsService.standardizeCustomFields(
                [],
                this.customInputsService.filterByCategory(customFields, etxn.tx.org_category_id)
              )
            )
          )
      )
    );
    from(this.loaderService.showLoader('Please wait...', 10000))
      .pipe(
        switchMap(() =>
          combineLatest([
            this.etxn$,
            selectedPaymentMode$,
            selectedProject$,
            selectedSubCategory$,
            this.txnFields$,
            selectedReport$,
            selectedCostCenter$,
            selectedCustomInputs$,
            this.mileageConfig$,
            defaultPaymentMode$,
            orgUserSettings$,
            orgSettings$,
            this.recentlyUsedValues$,
            this.recentlyUsedProjects$,
            this.recentlyUsedCostCenters$,
          ])
        ),
        take(1),
        finalize(() => from(this.loaderService.hideLoader()))
      )
      .subscribe(
        ([
          etxn,
          paymentMode,
          project,
          subCategory,
          txnFields,
          report,
          costCenter,
          customInputs,
          mileageConfig,
          defaultPaymentMode,
          orgUserSettings,
          orgSettings,
          recentValue,
          recentProjects,
          recentCostCenters,
        ]) => {
          const customInputValues = customInputs.map((customInput) => {
            const cpor =
              etxn.tx.custom_properties &&
              etxn.tx.custom_properties.find((customProp) => customProp.name === customInput.name);
            if (customInput.type === 'DATE') {
              return {
                name: customInput.name,
                value: (cpor && cpor.value && moment(new Date(cpor.value)).format('y-MM-DD')) || null,
              };
            } else {
              return {
                name: customInput.name,
                value: (cpor && cpor.value) || null,
              };
            }
          });

          // Check if auto-fills is enabled
          const isAutofillsEnabled =
            orgSettings.org_expense_form_autofills &&
            orgSettings.org_expense_form_autofills.allowed &&
            orgSettings.org_expense_form_autofills.enabled &&
            orgUserSettings.expense_form_autofills.allowed &&
            orgUserSettings.expense_form_autofills.enabled;

          // Check if recent projects exist
          const doRecentProjectIdsExist =
            isAutofillsEnabled &&
            recentValue &&
            recentValue.recent_project_ids &&
            recentValue.recent_project_ids.length > 0;

          if (recentProjects && recentProjects.length > 0) {
            this.recentProjects = recentProjects.map((item) => ({ label: item.project_name, value: item }));
          }

          /* Autofill project during these cases:
           * 1. Autofills is allowed and enabled
           * 2. During add expense - When project field is empty
           * 3. During edit expense - When the expense is in draft state and there is no project already added
           * 4. When there exists recently used project ids to auto-fill
           */
          if (
            doRecentProjectIdsExist &&
            (!etxn.tx.id || (etxn.tx.id && etxn.tx.state === 'DRAFT' && !etxn.tx.project_id))
          ) {
            const autoFillProject = recentProjects && recentProjects.length > 0 && recentProjects[0];

            if (autoFillProject) {
              project = autoFillProject;
              this.presetProjectId = project.project_id;
            }
          }

          // Check if recent cost centers exist
          const doRecentCostCenterIdsExist =
            isAutofillsEnabled &&
            recentValue &&
            recentValue.recent_cost_center_ids &&
            recentValue.recent_cost_center_ids.length > 0;

          if (recentCostCenters && recentCostCenters.length > 0) {
            this.recentCostCenters = recentCostCenters;
          }

          /* Autofill cost center during these cases:
           * 1. Autofills is allowed and enabled
           * 2. During add expense - When cost center field is empty
           * 3. During edit expense - When the expense is in draft state and there is no cost center already added - optional
           * 4. When there exists recently used cost center ids to auto-fill
           */
          if (
            doRecentCostCenterIdsExist &&
            (!etxn.tx.id || (etxn.tx.id && etxn.tx.state === 'DRAFT' && !etxn.tx.cost_center_id))
          ) {
            const autoFillCostCenter = recentCostCenters && recentCostCenters.length > 0 && recentCostCenters[0];

            if (autoFillCostCenter) {
              costCenter = autoFillCostCenter.value;
              this.presetCostCenterId = autoFillCostCenter.value.id;
            }
          }

          // Check if recent location exists
          const isRecentLocationPresent =
            orgSettings.org_expense_form_autofills &&
            orgSettings.org_expense_form_autofills.allowed &&
            orgSettings.org_expense_form_autofills.enabled &&
            orgUserSettings.expense_form_autofills.allowed &&
            orgUserSettings.expense_form_autofills.enabled &&
            recentValue &&
            recentValue.recent_start_locations &&
            recentValue.recent_start_locations.length > 0;
          if (isRecentLocationPresent) {
            this.presetLocation = recentValue.recent_start_locations[0];
          }

          this.fg.patchValue({
            mileage_vehicle_type: etxn.tx.mileage_vehicle_type,
            dateOfSpend: etxn.tx.txn_dt && moment(etxn.tx.txn_dt).format('y-MM-DD'),
            paymentMode: paymentMode || defaultPaymentMode,
            purpose: etxn.tx.purpose,
            route: {
              mileageLocations: etxn.tx.locations,
              distance: etxn.tx.distance,
              roundTrip: etxn.tx.mileage_is_round_trip,
            },
            project,
            billable: etxn.tx.billable,
            sub_category: subCategory,
            costCenter,
            duplicate_detection_reason: etxn.tx.user_reason_for_duplicate_expenses,
            report,
          });

          this.initialFetch = false;

          setTimeout(() => {
            this.fg.controls.custom_inputs.patchValue(customInputValues);
            this.formInitializedFlag = true;
          }, 1000);
        }
      );
  }
Example #19
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
BatchOperation = <T extends Obj>(props: IBatchProps<T>) => {
  const defaultOperationRender = (op: IRowActions) => {
    return <div>{allWordsFirstLetterUpper(op.name)}</div>;
  };
  const {
    rowKey = 'id',
    dataSource,
    onSelectChange,
    operations = [],
    selectedKeys = emptyKeys,
    operationRender = defaultOperationRender,
  } = props;
  const [{ checkAll, indeterminate }, updater, update] = useUpdate({
    checkAll: false,
    indeterminate: false,
  });
  const getKey = React.useCallback((item: T) => (typeof rowKey === 'function' ? rowKey(item) : item[rowKey]), [rowKey]);

  React.useEffect(() => {
    const allKeys: React.Key[] = map(dataSource, getKey);
    const curChosenKeys = intersection(allKeys, selectedKeys);
    update({
      checkAll: curChosenKeys.length === allKeys.length && allKeys.length > 0,
      indeterminate: curChosenKeys.length !== 0 && curChosenKeys.length < allKeys.length,
    });
  }, [update, dataSource, rowKey, selectedKeys, getKey]);

  const onCheckAllChange = () => {
    const allKeys: React.Key[] = map(dataSource, getKey);
    if (checkAll) {
      onSelectChange(difference(selectedKeys, allKeys));
    } else {
      onSelectChange(uniq(selectedKeys.concat(allKeys)));
    }
  };

  const visibleOperations = operations.filter((op) =>
    typeof op.isVisible === 'function' ? op.isVisible(selectedKeys) : true,
  );
  const dropdownMenu = (
    <Menu
      theme="dark"
      selectable
      onClick={({ key }) => {
        const op = visibleOperations.find((a) => a.key === key);
        if (op) {
          const result = op.onClick(selectedKeys);
          if (isPromise(result)) {
            result.then(() => onSelectChange([]));
          } else {
            onSelectChange([]);
          }
        }
      }}
    >
      {map(visibleOperations, (opItem) => {
        return (
          <Menu.Item key={opItem.key} disabled={opItem.disabled}>
            {operationRender(opItem)}
          </Menu.Item>
        );
      })}
    </Menu>
  );

  return (
    <div className="flex items-center">
      <Checkbox className="ml-0.5 mr-2" indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll} />
      <span className="mr-2">
        {`${i18n.t('{name} items selected', {
          name: selectedKeys?.length || 0,
        })}`}
      </span>
      {visibleOperations.length > 1 ? (
        <Dropdown
          overlay={dropdownMenu}
          overlayClassName="dice-cp-table-batch-operations"
          getPopupContainer={(triggerNode) => triggerNode.parentElement as HTMLElement}
        >
          <Button className="flex items-center bg-default-06 border-transparent text-default-8">
            {i18n.t('Batch Operations')}
            <ErdaIcon size="18" type="caret-down" className="ml-1 text-default-4" />
          </Button>
        </Dropdown>
      ) : visibleOperations.length === 1 ? (
        <Button
          className="flex items-center bg-default-06 border-transparent text-default-8"
          disabled={visibleOperations[0].disabled}
          onClick={() => {
            const result = visibleOperations[0].onClick(selectedKeys);
            if (isPromise(result)) {
              result.then(() => onSelectChange([]));
            } else {
              onSelectChange([]);
            }
          }}
        >
          {visibleOperations[0].name}
        </Button>
      ) : null}
    </div>
  );
}
Example #20
Source File: useSelection.tsx    From gio-design with Apache License 2.0 4 votes vote down vote up
useSelection = <RecordType,>(
  data: RecordType[],
  rowSelection: RowSelection<RecordType> | undefined,
  config: {
    rowKey?: TableProps<RecordType>['rowKey'];
  }
): [(columns: ColumnsType<RecordType>) => ColumnsType<RecordType>, Key[]] => {
  const { onChange, selectedRowKeys, columnWidth = 52, fixed, getCheckboxProps } = rowSelection || {};
  const { rowKey } = config;

  const [localSelectedRowKeys, setLocalSelectedRowKeys] = useControlledState<Key[]>(selectedRowKeys, []);

  // 获取当前页所有row的key
  const currentPageRowKeys = useMemo(() => flatten(data.map((item) => getRowKey(item, rowKey))), [data]);

  const isAllChecked = useMemo(
    () => intersection(localSelectedRowKeys, currentPageRowKeys).length === currentPageRowKeys.length,
    [currentPageRowKeys, localSelectedRowKeys]
  );
  const atLeastOneChecked = useMemo(
    () => intersection(currentPageRowKeys, localSelectedRowKeys).length > 0,
    [currentPageRowKeys, localSelectedRowKeys]
  );
  const isPartChecked = useMemo(() => !isAllChecked && atLeastOneChecked, [isAllChecked, atLeastOneChecked]);
  const isAllDisabled = useMemo(
    () => data.every((item) => getCheckboxProps?.(item)?.disabled),
    [data, getCheckboxProps]
  );

  const isRowAllSelected = (keys: any) => {
    const childrenKeys = Array.isArray(keys) ? keys.slice(1, keys.length) : [keys];

    return childrenKeys.every((keyItem) => localSelectedRowKeys.includes(keyItem));
  };

  const isRowPartSelected = (keys: any) =>
    Array.isArray(keys) ? keys.slice(1, keys.length).some((keyItem) => localSelectedRowKeys.includes(keyItem)) : false;

  const allDisabledKey: string[] = [];
  // 获取所有的disabled选项的key
  const getAllDisabledKey = (dataTree: any) => {
    dataTree.forEach((item: any) => {
      if (isFunction(getCheckboxProps) && getCheckboxProps(item).disabled) {
        Array.isArray(getRowKey(item, rowKey))
          ? allDisabledKey.push(...(getRowKey(item, rowKey) as any))
          : allDisabledKey.push(getRowKey(item, rowKey) as any);
      } else if (item.children) {
        getAllDisabledKey(item.children);
      }
    });
  };

  // 所有的子元素全部disabled
  const isParentDisabled = (keys: Key | Key[]) =>
    Array.isArray(keys) ? keys.slice(1).every((key) => allDisabledKey.includes(`${key}`)) : false;

  // 父元素disabled
  const isChildDisabled = (keys: Key | Key[]) => (Array.isArray(keys) ? false : allDisabledKey.includes(`${keys}`));

  const getSelectRows = useCallback(
    (_selectedRowKeys) => data.filter((item) => _selectedRowKeys.includes(getRowKey(item, rowKey))),
    [data]
  );

  // 获取父节点的keys
  const getParentKeys = (dataTree: any, keys: Key | Key[]): Key[] => {
    if (!Array.isArray(keys)) {
      if (data.some((item: any) => item.key === keys)) {
        return [];
      }

      // eslint-disable-next-line no-restricted-syntax
      for (let item of dataTree) {
        if (item.children) {
          if (item.children.some((child: any) => child.key === keys)) {
            return getRowKey(item, rowKey) as any;
          }

          return getParentKeys(item.children, keys);
        }
      }
    }

    return [];
  };

  // 更新parent的check状态
  const updateParentCheck = (selectedKeys: Key[], childKey: Key | Key[]): any => {
    const parentKeys = getParentKeys(data, childKey);
    if (parentKeys.length) {
      /** @todo: 无法执行此代码  */
      // if (parentKeys.slice(1).every((key) => selectedKeys.includes(key))) {
      //   // 向上递归更新状态,直至根结点
      //   return updateParentCheck(flattenDeep(union(selectedKeys, flattenDeep(parentKeys))), parentKeys[0]);
      // }
      return selectedKeys.filter((key) => key !== parentKeys[0]);
    }
    return selectedKeys;
  };

  const selectionColumn: ColumnType<RecordType> = {
    title: (
      <Checkbox
        checked={atLeastOneChecked}
        indeterminate={isPartChecked}
        onClick={(e) => e.stopPropagation()}
        onChange={(e) => {
          getAllDisabledKey(data);
          const latestLocalSelectedRowKeys = e.target.checked
            ? flattenDeep(difference(union(localSelectedRowKeys, currentPageRowKeys), allDisabledKey))
            : flattenDeep(difference(localSelectedRowKeys, currentPageRowKeys, allDisabledKey));
          setLocalSelectedRowKeys(latestLocalSelectedRowKeys);
          onChange?.(latestLocalSelectedRowKeys, getSelectRows(latestLocalSelectedRowKeys));
        }}
        disabled={isAllDisabled}
      />
    ),
    fixed,
    key: 'selection',
    align: 'center',
    width: columnWidth,
    render: (...rest) => {
      getAllDisabledKey(data);
      const key = getRowKey(rest[1], rowKey);
      const thisCheckboxProps = getCheckboxProps?.(rest[1]) || {};
      const { tooltipProps, disabled, ...restCheckboxProps } = thisCheckboxProps;
      const contentNode = (
        <div>
          <Checkbox
            {...restCheckboxProps}
            disabled={disabled || isParentDisabled(key) || isChildDisabled(key)}
            indeterminate={!isRowAllSelected(key) && isRowPartSelected(key)}
            checked={
              Array.isArray(key)
                ? key.some((keyItem) => localSelectedRowKeys.includes(keyItem))
                : localSelectedRowKeys.includes(key)
            }
            onClick={(e) => e.stopPropagation()}
            onChange={(e) => {
              getAllDisabledKey(data);
              const latestLocalSelectedRowKeys = e.target.checked
                ? flattenDeep(difference(union(localSelectedRowKeys, flattenDeep([key])), allDisabledKey))
                : flattenDeep(difference(localSelectedRowKeys, flattenDeep([key]), allDisabledKey));
              setLocalSelectedRowKeys(latestLocalSelectedRowKeys);

              const updatedSelectedRowKeys = updateParentCheck(latestLocalSelectedRowKeys, key);
              setLocalSelectedRowKeys(updatedSelectedRowKeys);

              onChange?.(updatedSelectedRowKeys, getSelectRows(updatedSelectedRowKeys));
            }}
          >
            {disabled ? null : undefined}
          </Checkbox>
        </div>
      );
      return disabled ? (
        <Tooltip placement="topLeft" arrowPointAtCenter {...tooltipProps}>
          <span>{contentNode}</span>
        </Tooltip>
      ) : (
        <Tooltip placement="topLeft" arrowPointAtCenter {...tooltipProps}>
          {contentNode}
        </Tooltip>
      );
    },
  };

  const transformSelectionPipeline = useCallback(
    (columns: ColumnsType<RecordType>) => (!isUndefined(rowSelection) ? [selectionColumn, ...columns] : columns),
    [selectionColumn, rowSelection]
  );

  return [transformSelectionPipeline, localSelectedRowKeys];
}
Example #21
Source File: base-list.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
BatchOperation = <T extends unknown>(props: IBatchProps<T>) => {
  const { rowKey, dataSource, onSelectChange, execOperation, selectedRowKeys = emptyKeys, batchRowsHandle } = props;

  const selectableData = React.useMemo(() => dataSource.filter((item) => item.selectable !== false), [dataSource]);

  const [{ checkAll, indeterminate }, updater, update] = useUpdate({
    checkAll: false,
    indeterminate: false,
  });

  React.useEffect(() => {
    const allKeys = map(selectableData, rowKey);
    const curChosenKeys = intersection(allKeys, selectedRowKeys);
    update({
      checkAll: !!(curChosenKeys.length && curChosenKeys.length === allKeys.length),
      indeterminate: !!(curChosenKeys.length && curChosenKeys.length < allKeys.length),
    });
  }, [update, selectableData, rowKey, selectedRowKeys]);

  const optMenus = React.useMemo(() => {
    const { options } = batchRowsHandle?.serverData;
    return options.map((mItem) => {
      const { allowedRowIDs, forbiddenRowIDs } = mItem;
      const validChosenOpt =
        intersection(selectedRowKeys, allowedRowIDs || selectedRowKeys).length === selectedRowKeys.length &&
        intersection(selectedRowKeys, forbiddenRowIDs).length === 0;

      const disabledProps = selectedRowKeys?.length
        ? {
            disabled: has(mItem, 'disabled') ? mItem.disabled : !validChosenOpt,
            disabledTip: i18n.t('exist item which not match operation'),
          }
        : { disabled: true, disabledTip: i18n.t('no items selected') };
      const reMenu = {
        ...mItem,
        ...disabledProps,
      };
      return reMenu;
    });
  }, [batchRowsHandle, selectedRowKeys]);

  const dropdownMenu = (
    <Menu theme="dark">
      {map(optMenus, (mItem) => {
        return (
          <Menu.Item key={mItem.id} disabled={!!mItem.disabled}>
            <OperationAction
              operation={{ ...mItem, ...batchRowsHandle }}
              onClick={() =>
                execOperation({
                  key: 'batchRowsHandle',
                  ...batchRowsHandle,
                  clientData: { dataRef: mItem, selectedOptionsID: mItem.id, selectedRowIDs: selectedRowKeys },
                })
              }
              tipProps={{ placement: 'right' }}
            >
              <div className="flex-h-center">
                {mItem.icon ? (
                  <ErdaIcon type={typeof mItem.icon === 'string' ? mItem.icon : mItem.icon.type} className="mr-1" />
                ) : null}
                <span>{mItem.text}</span>
              </div>
            </OperationAction>
          </Menu.Item>
        );
      })}
    </Menu>
  );

  const onCheckAllChange = () => {
    const allKeys = map(selectableData, rowKey);
    if (checkAll) {
      onSelectChange(difference(selectedRowKeys, allKeys));
    } else {
      onSelectChange(compact(selectedRowKeys.concat(allKeys)));
    }
  };

  return (
    <div className="flex items-center">
      <Checkbox className="mr-2" indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll} />
      <span className="mr-2">{`${i18n.t('{name} items selected', {
        name: selectedRowKeys?.length || 0,
      })}`}</span>
      <Dropdown overlay={dropdownMenu} zIndex={1000}>
        <Button className="flex items-center">
          {i18n.t('Batch Operations')}
          <ErdaIcon size="18" type="caret-down" className="ml-1 text-default-4" />
        </Button>
      </Dropdown>
    </div>
  );
}
Example #22
Source File: route.ts    From raydium-sdk with GNU General Public License v3.0 4 votes vote down vote up
static async makeSwapTransaction(params: RouteSwapTransactionParams) {
    const { connection, fromPoolKeys, toPoolKeys, userKeys, amountIn, amountOut, fixedSide, config } = params;
    const { tokenAccounts, owner } = userKeys;

    logger.debug("amountIn:", amountIn);
    logger.debug("amountOut:", amountOut);
    logger.assertArgument(
      !amountIn.isZero() && !amountOut.isZero(),
      "amounts must greater than zero",
      "currencyAmounts",
      {
        amountIn: amountIn.toFixed(),
        amountOut: amountOut.toFixed(),
      },
    );

    const { bypassAssociatedCheck } = {
      // default
      ...{ bypassAssociatedCheck: false },
      // custom
      ...config,
    };

    // handle currency in & out (convert SOL to WSOL)
    const tokenIn = amountIn instanceof TokenAmount ? amountIn.token : Token.WSOL;
    const tokenOut = amountOut instanceof TokenAmount ? amountOut.token : Token.WSOL;

    const tokenAccountIn = await this._selectTokenAccount({
      tokenAccounts,
      mint: tokenIn.mint,
      owner,
      config: { associatedOnly: false },
    });
    const tokenAccountOut = await this._selectTokenAccount({
      tokenAccounts,
      mint: tokenOut.mint,
      owner,
    });

    const fromPoolMints = [fromPoolKeys.baseMint.toBase58(), fromPoolKeys.quoteMint.toBase58()];
    const toPoolMints = [toPoolKeys.baseMint.toBase58(), toPoolKeys.quoteMint.toBase58()];
    const intersectionMints = intersection(fromPoolMints, toPoolMints);
    const _middleMint = intersectionMints[0];
    const middleMint = new PublicKey(_middleMint);
    const tokenAccountMiddle = await this._selectTokenAccount({
      tokenAccounts,
      mint: middleMint,
      owner,
    });

    const [amountInRaw, amountOutRaw] = [amountIn.raw, amountOut.raw];

    const setupInstructions: TransactionInstruction[] = [];
    const setupSigners: Signer[] = [];
    const swapInstructions: TransactionInstruction[] = [];

    const _tokenAccountIn = await this._handleTokenAccount({
      connection,
      side: "in",
      amount: amountInRaw,
      mint: tokenIn.mint,
      tokenAccount: tokenAccountIn,
      owner,
      frontInstructions: setupInstructions,
      signers: setupSigners,
      bypassAssociatedCheck,
    });
    const _tokenAccountOut = await this._handleTokenAccount({
      connection,
      side: "out",
      amount: 0,
      mint: tokenOut.mint,
      tokenAccount: tokenAccountOut,
      owner,
      frontInstructions: setupInstructions,
      signers: setupSigners,
      bypassAssociatedCheck,
    });
    const _tokenAccountMiddle = await this._handleTokenAccount({
      connection,
      side: "in",
      amount: 0,
      mint: middleMint,
      tokenAccount: tokenAccountMiddle,
      owner,
      frontInstructions: setupInstructions,
      signers: setupSigners,
      bypassAssociatedCheck,
    });

    swapInstructions.push(
      ...this.makeSwapInstruction({
        fromPoolKeys,
        toPoolKeys,
        userKeys: {
          inTokenAccount: _tokenAccountIn,
          outTokenAccount: _tokenAccountOut,
          middleTokenAccount: _tokenAccountMiddle,
          middleStatusAccount: await this.getAssociatedMiddleStatusAccount({
            programId: ROUTE_PROGRAM_ID_V1,
            fromPoolId: fromPoolKeys.id,
            middleMint,
            owner,
          }),
          owner,
        },
        amountIn: amountInRaw,
        amountOut: amountOutRaw,
        fixedSide,
      }),
    );

    let setupTransaction: UnsignedTransactionAndSigners | null = null;
    let swapTransaction: UnsignedTransactionAndSigners | null = null;

    if (setupInstructions.length > 0) {
      setupTransaction = {
        transaction: new Transaction().add(...setupInstructions),
        signers: setupSigners,
      };
    }
    if (swapInstructions.length > 0) {
      swapTransaction = {
        transaction: new Transaction().add(...swapInstructions),
        signers: [],
      };
    }

    return { setupTransaction, swapTransaction };
  }
Example #23
Source File: route.ts    From raydium-sdk with GNU General Public License v3.0 4 votes vote down vote up
// static makeSwapInFixedOutInstruction() {}

  // static makeSwapOutFixedOutInstruction() {}

  /* ================= compute data ================= */
  static computeAmountOut({
    fromPoolKeys,
    toPoolKeys,
    fromPoolInfo,
    toPoolInfo,
    amountIn,
    currencyOut,
    slippage,
  }: RouteComputeAmountOutParams) {
    const { swap: fromPoolSwapEnabled } = Liquidity.getEnabledFeatures(fromPoolInfo);
    const { swap: toPoolSwapEnabled } = Liquidity.getEnabledFeatures(toPoolInfo);
    logger.assertArgument(fromPoolSwapEnabled && toPoolSwapEnabled, "pools swap not enabled", "pools", {
      fromPoolKeys,
      toPoolKeys,
      fromPoolInfo,
      toPoolInfo,
    });

    const tokenIn = amountIn instanceof TokenAmount ? amountIn.token : Token.WSOL;
    const tokenOut = currencyOut instanceof Token ? currencyOut : Token.WSOL;

    logger.assertArgument(
      Liquidity.includesToken(tokenIn, fromPoolKeys) && Liquidity.includesToken(tokenOut, toPoolKeys),
      "pools cannot be routed",
      "pools",
      {
        fromPoolKeys,
        toPoolKeys,
      },
    );

    const fromPoolMints = [fromPoolKeys.baseMint.toBase58(), fromPoolKeys.quoteMint.toBase58()];
    const toPoolMints = [toPoolKeys.baseMint.toBase58(), toPoolKeys.quoteMint.toBase58()];
    const mints = [...fromPoolMints, ...toPoolMints];
    const decimals = [
      fromPoolInfo.baseDecimals,
      fromPoolInfo.quoteDecimals,
      toPoolInfo.baseDecimals,
      toPoolInfo.quoteDecimals,
    ];
    const mintIn = tokenIn.mint.toBase58();
    const mintOut = tokenOut.mint.toBase58();

    const xorMints = xor(fromPoolMints, toPoolMints);
    logger.assertArgument(
      xorMints.length === 2 && xorMints.includes(mintIn) && xorMints.includes(mintOut),
      "xor tokens not match",
      "pools",
      {
        fromPoolKeys,
        toPoolKeys,
      },
    );

    const intersectionMints = intersection(fromPoolMints, toPoolMints);
    logger.assertArgument(intersectionMints.length === 1, "cannot found middle token of two pools", "pools", {
      fromPoolKeys,
      toPoolKeys,
    });

    const _middleMint = intersectionMints[0];
    const index = mints.indexOf(_middleMint);
    // logger.assertArgument(index !== -1, "cannot found middle token", "pools", {
    //   fromPoolKeys,
    //   toPoolKeys,
    // });
    const middleMintDecimals = decimals[index];
    const middleMint = new PublicKey(_middleMint);
    const middleToken = new Token(middleMint, middleMintDecimals);

    logger.debug("from pool:", fromPoolKeys);
    logger.debug("to pool:", toPoolKeys);
    logger.debug("intersection mints:", intersectionMints);
    logger.debug("xor mints:", xorMints);
    logger.debug("middleMint:", _middleMint);

    // TODO slippage and amount out
    const {
      amountOut: middleAmountOut,
      minAmountOut: minMiddleAmountOut,
      priceImpact: firstPriceImpact,
      fee: firstFee,
    } = Liquidity.computeAmountOut({
      poolKeys: fromPoolKeys,
      poolInfo: fromPoolInfo,
      amountIn,
      currencyOut: middleToken,
      slippage,
    });
    const {
      amountOut,
      minAmountOut,
      priceImpact: secondPriceImpact,
      fee: secondFee,
    } = Liquidity.computeAmountOut({
      poolKeys: toPoolKeys,
      poolInfo: toPoolInfo,
      amountIn: minMiddleAmountOut,
      currencyOut,
      slippage,
    });

    let executionPrice: Price | null = null;
    const amountInRaw = amountIn.raw;
    const amountOutRaw = amountOut.raw;
    const currencyIn = amountIn instanceof TokenAmount ? amountIn.token : amountIn.currency;
    if (!amountInRaw.isZero() && !amountOutRaw.isZero()) {
      executionPrice = new Price(currencyIn, amountInRaw, currencyOut, amountOutRaw);
      logger.debug("executionPrice:", `1 ${currencyIn.symbol}${executionPrice.toFixed()} ${currencyOut.symbol}`);
      logger.debug(
        "executionPrice invert:",
        `1 ${currencyOut.symbol}${executionPrice.invert().toFixed()} ${currencyIn.symbol}`,
      );
    }

    return {
      // middleAmountOut,
      // minMiddleAmountOut,
      amountOut,
      minAmountOut,
      executionPrice,
      priceImpact: firstPriceImpact.add(secondPriceImpact),
      fee: [firstFee, secondFee],
    };
  }
Example #24
Source File: table.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
BatchOperation = <T extends unknown>(props: IBatchProps<T>) => {
  const { rowKey, dataSource, onSelectChange, execOperation, selectedRowKeys = emptyArr, batchRowsHandle } = props;

  const [{ checkAll, indeterminate }, updater, update] = useUpdate({
    checkAll: false,
    indeterminate: false,
  });

  React.useEffect(() => {
    const allKeys = map(dataSource, rowKey);
    const curChosenKeys = intersection(allKeys, selectedRowKeys);
    update({
      checkAll: !!(curChosenKeys.length && curChosenKeys.length === allKeys.length),
      indeterminate: !!(curChosenKeys.length && curChosenKeys.length < allKeys.length),
    });
  }, [update, dataSource, rowKey, selectedRowKeys]);

  const optMenus = React.useMemo(() => {
    const { options } = batchRowsHandle?.serverData;
    return options.map((mItem) => {
      const { allowedRowIDs, forbiddenRowIDs } = mItem;
      const validChosenOpt =
        intersection(selectedRowKeys, allowedRowIDs || selectedRowKeys).length === selectedRowKeys.length &&
        intersection(selectedRowKeys, forbiddenRowIDs).length === 0;

      const disabledProps = selectedRowKeys?.length
        ? {
            disabled: has(mItem, 'disabled') ? mItem.disabled : !validChosenOpt,
            disabledTip: i18n.t('exist item which not match operation'),
          }
        : { disabled: true, disabledTip: i18n.t('no items selected') };
      const reMenu = {
        ...mItem,
        ...disabledProps,
      };
      return reMenu;
    });
  }, [batchRowsHandle, selectedRowKeys]);

  const dropdownMenu = (
    <Menu theme="dark">
      {map(optMenus, (mItem) => {
        return (
          <Menu.Item key={mItem.id} disabled={!!mItem.disabled}>
            <OperationAction
              operation={{ ...mItem, ...batchRowsHandle }}
              onClick={() =>
                execOperation({
                  key: 'batchRowsHandle',
                  ...batchRowsHandle,
                  clientData: { dataRef: mItem, selectedOptionsID: mItem.id, selectedRowIDs: selectedRowKeys },
                })
              }
              tipProps={{ placement: 'right' }}
            >
              <div>{mItem.text}</div>
            </OperationAction>
          </Menu.Item>
        );
      })}
    </Menu>
  );

  const onCheckAllChange = () => {
    const allKeys = map(dataSource, rowKey);
    if (checkAll) {
      onSelectChange(difference(selectedRowKeys, allKeys));
    } else {
      onSelectChange(compact(selectedRowKeys.concat(allKeys)));
    }
  };

  return (
    <div className="flex items-center">
      <Checkbox className="mx-2" indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll} />
      <span className="mr-2">{`${i18n.t('{name} items selected', {
        name: selectedRowKeys?.length || 0,
      })}`}</span>
      <Dropdown
        overlay={dropdownMenu}
        zIndex={1000}
        overlayClassName="dice-cp-table-batch-operations"
        getPopupContainer={(triggerNode) => triggerNode.parentElement as HTMLElement}
      >
        <Button className="flex items-center">
          {i18n.t('Batch Operations')}
          <ErdaIcon size="18" type="caret-down" className="ml-1 text-black-2" />
        </Button>
      </Dropdown>
    </div>
  );
}
Example #25
Source File: Entries.tsx    From subscan-multisig-react with Apache License 2.0 4 votes vote down vote up
// eslint-disable-next-line complexity
export function Entries({
  source,
  isConfirmed,
  isCancelled,
  account,
  loading,
  totalCount,
  currentPage,
  onChangePage,
}: EntriesProps) {
  const { t } = useTranslation();
  const isInjected = useIsInjected();
  const { network } = useApi();

  const renderAction = useCallback(
    // eslint-disable-next-line complexity
    (row: Entry) => {
      if (row.status) {
        return <span>{t(`status.${row.status}`)}</span>;
      }

      const actions: TxActionType[] = [];
      // eslint-disable-next-line react/prop-types
      const pairs = (account.meta?.addressPair ?? []) as AddressPair[];
      const injectedAccounts: string[] = pairs.filter((pair) => isInjected(pair.address)).map((pair) => pair.address);

      if (injectedAccounts.includes(row.depositor)) {
        actions.push('cancel');
      }

      const localAccountInMultisigPairList = intersection(
        injectedAccounts,
        pairs.map((pair) => pair.address)
      );
      const approvedLocalAccounts = intersection(localAccountInMultisigPairList, row.approvals);

      if (approvedLocalAccounts.length !== localAccountInMultisigPairList.length) {
        actions.push('approve');
      }

      if (actions.length === 0) {
        // eslint-disable-next-line react/prop-types
        if (row.approvals && row.approvals.length === account.meta.threshold) {
          actions.push('pending');
        }
      }

      return (
        <Space>
          {actions.map((action) => {
            if (action === 'pending') {
              return (
                <Button key={action} disabled>
                  {t(action)}
                </Button>
              );
            } else if (action === 'approve') {
              return <TxApprove key={action} entry={row} />;
            } else {
              return <TxCancel key={action} entry={row} />;
            }
          })}
        </Space>
      );
    },
    [account.meta?.addressPair, account.meta.threshold, isInjected, t]
  );

  const columns: ColumnsType<Entry> = [
    {
      title: t(isConfirmed || isCancelled ? 'extrinsic_index' : 'call_data'),
      dataIndex: isConfirmed || isCancelled ? 'extrinsicIdx' : 'hexCallData',
      width: 300,
      align: 'left',
      // eslint-disable-next-line complexity
      render(data: string) {
        let extrinsicHeight = '';
        let extrinsicIndex = '';
        if ((isConfirmed || isCancelled) && data.split('-').length > 1) {
          extrinsicHeight = data.split('-')[0];
          extrinsicIndex = data.split('-')[1];
        }

        return !(isConfirmed || isCancelled) ? (
          <>
            <Typography.Text copyable={!isEmpty(data) && { text: data }}>
              {!isEmpty(data)
                ? // ? `${data.substring(0, CALL_DATA_LENGTH)}${data.length > CALL_DATA_LENGTH ? '...' : ''}`
                  toShortString(data, CALL_DATA_LENGTH)
                : '-'}
            </Typography.Text>
          </>
        ) : (
          <SubscanLink extrinsic={{ height: extrinsicHeight, index: extrinsicIndex }}>{data}</SubscanLink>
        );
      },
    },
    {
      title: t('actions'),
      dataIndex: 'callDataJson',
      align: 'left',
      render: renderMethod,
    },
    {
      title: t('progress'),
      dataIndex: 'approvals',
      align: 'left',
      render(approvals: string[]) {
        const cur = (approvals && approvals.length) || 0;

        return cur + '/' + account.meta.threshold;
      },
    },
    {
      title: t('status.index'),
      key: 'status',
      align: 'left',
      render: (_, row) => renderAction(row),
    },
  ];

  const expandedRowRender = (entry: Entry) => {
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const progressColumnsNested: ColumnsType<any> = [
      { dataIndex: 'name', width: 100 },
      {
        width: 400,
        dataIndex: 'address',
        render: (address) => (
          <Space size="middle">
            <BaseIdentityIcon theme="polkadot" size={32} value={address} />
            <SubscanLink address={address} copyable />
          </Space>
        ),
      },
      {
        width: 250,
        key: 'status',
        render: (_, pair) => renderMemberStatus(entry, pair, network, !isCancelled && !isConfirmed),
      },
    ];
    // const callDataJson = entry.callData?.toJSON() ?? {};
    const args: Required<ArgObj>[] = ((entry.meta?.args ?? []) as Required<ArgObj>[]).map((arg) => {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const value = (entry.callDataJson?.args as any)[arg?.name ?? ''];

      return { ...arg, value };
    });

    return (
      <div className="record-expand bg-gray-100 py-3 px-5">
        <div className=" text-black-800 text-base leading-none mb-3">{t('progress')}</div>
        <div className="members">
          <Table
            columns={progressColumnsNested}
            dataSource={account.meta.addressPair as { key: string; name: string; address: string }[]}
            pagination={false}
            bordered
            rowKey="address"
            showHeader={false}
            className="mb-4 mx-4"
          />
        </div>
        <div className=" text-black-800 text-base leading-none my-3">{t('parameters')}</div>

        <Args
          args={args}
          className="mb-4 mx-4"
          section={entry.callDataJson?.section}
          method={entry.callDataJson?.method}
        />
      </div>
    );
  };

  return (
    <div className="record-table">
      <Table
        loading={loading}
        dataSource={source}
        columns={columns}
        rowKey={(record) => record.callHash ?? (record.blockHash as string)}
        pagination={
          isConfirmed || isCancelled
            ? {
                total: totalCount,
                pageSize: 10,
                current: currentPage,
                onChange: onChangePage,
              }
            : false
        }
        expandable={{
          expandedRowRender,
          expandIcon: genExpandIcon(),
          expandIconColumnIndex: 4,
        }}
        className="lg:block hidden"
      ></Table>

      <Space direction="vertical" className="lg:hidden block">
        {source.map((data) => {
          const { address, hash, callData, approvals } = data;
          const approvedCount = approvals.length || 0;
          const threshold = (account.meta.threshold as number) || 1;

          return (
            <Collapse key={address} expandIcon={() => <></>} className="wallet-collapse">
              <Panel
                header={
                  <Space direction="vertical" className="w-full mb-4">
                    <Typography.Text className="mr-4" copyable>
                      {hash}
                    </Typography.Text>

                    <div className="flex items-center">
                      <Typography.Text>{renderMethod(callData)}</Typography.Text>

                      <Progress
                        /* eslint-disable-next-line no-magic-numbers */
                        percent={parseInt(String((approvedCount / threshold) * 100), 10)}
                        steps={threshold}
                        className="ml-4"
                      />
                    </div>
                  </Space>
                }
                key={address}
                extra={renderAction(data)}
                className="overflow-hidden mb-4"
              >
                <MemberList
                  data={account}
                  statusRender={(pair) => renderMemberStatus(data, pair, network, !isCancelled && !isConfirmed)}
                />
              </Panel>
            </Collapse>
          );
        })}

        {!source.length && <Empty />}
      </Space>
    </div>
  );
}
Example #26
Source File: table.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
BatchOperation = (props: IBatchProps) => {
  const {
    rowKey,
    dataSource,
    onSelectChange,
    operations,
    chosenItems,
    execOperation,
    selectedRowKeys = emptyArr,
    batchOperations,
  } = props;

  const [{ checkAll, indeterminate }, updater, update] = useUpdate({
    checkAll: false,
    indeterminate: false,
  });

  React.useEffect(() => {
    const allKeys = map(dataSource, rowKey);
    const curChosenKeys = intersection(allKeys, selectedRowKeys);
    update({
      checkAll: !!(curChosenKeys.length && curChosenKeys.length === allKeys.length),
      indeterminate: !!(curChosenKeys.length && curChosenKeys.length < allKeys.length),
    });
  }, [update, dataSource, rowKey, selectedRowKeys]);

  const optMenus = React.useMemo(() => {
    const fullMenus = batchOperations.map((btItem) => find(operations, (opItem) => opItem.key === btItem));
    const chosenOpts = intersection(...map(chosenItems, 'batchOperations'));
    return fullMenus.map((mItem) => {
      const disabledProps = selectedRowKeys?.length
        ? {
            disabled: has(mItem, 'disabled') ? mItem.disabled : !chosenOpts.includes(mItem.key),
            disabledTip: i18n.t('exist item which not match operation'),
          }
        : { disabled: true, disabledTip: i18n.t('no items selected') };
      const reMenu = {
        ...mItem,
        ...disabledProps,
      };
      return reMenu;
    });
  }, [batchOperations, chosenItems, operations, selectedRowKeys]);

  const dropdownMenu = (
    <Menu theme="dark">
      {map(optMenus, (mItem) => {
        return (
          <Menu.Item key={mItem.key} disabled={mItem.disabled}>
            <OperationAction
              operation={mItem}
              onClick={() => execOperation(mItem, { selectedRowKeys })}
              tipProps={{ placement: 'right' }}
            >
              <div>{mItem.text}</div>
            </OperationAction>
          </Menu.Item>
        );
      })}
    </Menu>
  );

  const onCheckAllChange = () => {
    const allKeys = map(dataSource, rowKey);
    if (checkAll) {
      onSelectChange(difference(selectedRowKeys, allKeys));
    } else {
      onSelectChange(compact(selectedRowKeys.concat(allKeys)));
    }
  };

  return (
    <div className="flex items-center">
      <Checkbox className="mx-2" indeterminate={indeterminate} onChange={onCheckAllChange} checked={checkAll} />
      <span className="mr-2">{`${i18n.t('{name} items selected', {
        name: selectedRowKeys?.length || 0,
      })}`}</span>
      <Dropdown
        overlay={dropdownMenu}
        zIndex={1000}
        overlayClassName="dice-cp-table-batch-operations"
        getPopupContainer={(triggerNode) => triggerNode.parentElement as HTMLElement}
      >
        <Button className="flex items-center bg-default-06 border-transparent text-default-8">
          {i18n.t('Batch Operations')}
          <ErdaIcon size="18" type="caret-down" className="ml-1 text-default-4" />
        </Button>
      </Dropdown>
    </div>
  );
}