@ant-design/icons#SearchOutlined TypeScript Examples
The following examples show how to use
@ant-design/icons#SearchOutlined.
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: general-search.editor.tsx From next-basics with GNU General Public License v3.0 | 6 votes |
export function GeneralSearchEditor({
nodeUid,
}: EditorComponentProps): React.ReactElement {
const node = useBuilderNode<GeneralSearchProperties>({ nodeUid });
const { placeholder } = node.$$parsedProperties;
return (
<EditorContainer nodeUid={nodeUid}>
<div className={styles.searchContainer}>
<div className={styles.placeholder}>
{smartDisplayForEvaluableString(placeholder, "", "<% … %>")}
</div>
<SearchOutlined className={styles.searchIcon} />
</div>
</EditorContainer>
);
}
Example #2
Source File: Searchbar.tsx From datart with Apache License 2.0 | 6 votes |
export function Searchbar({ placeholder, onSearch }: SearchbarProps) {
return (
<Wrapper className="search">
<Col span={24}>
<Input
prefix={<SearchOutlined className="icon" />}
placeholder={placeholder}
className="search-input"
bordered={false}
onChange={onSearch}
/>
</Col>
</Wrapper>
);
}
Example #3
Source File: metricsExplorer.tsx From fe-v5 with Apache License 2.0 | 6 votes |
MetricsExplorer: React.FC<MetricsExplorer> = ({ show, updateShow, metrics, insertAtCursor }) => {
const [filteredMetrics, setFilteredMetrics] = useState<string[]>(metrics);
function checkMetric(value: string) {
insertAtCursor(value);
updateShow(false);
}
useEffect(() => {
setFilteredMetrics(metrics);
}, [metrics]);
return (
<Modal className='metrics-explorer-modal' width={540} visible={show} title='Metrics Explorer' footer={null} onCancel={() => updateShow(false)}>
<Input
className='left-area-group-search'
prefix={<SearchOutlined />}
onPressEnter={(e) => {
e.preventDefault();
const value = e.currentTarget.value;
setFilteredMetrics(metrics.filter((metric) => metric.includes(value)));
}}
/>
<div className='metric-list' onClick={(e) => checkMetric((e.target as HTMLElement).innerText)}>
{filteredMetrics.map((metric) => (
<div className='metric-list-item' key={metric}>
{metric}
</div>
))}
</div>
</Modal>
);
}
Example #4
Source File: index.tsx From datart with Apache License 2.0 | 6 votes |
export function NotFoundPage() {
const t = useI18NPrefix('notfound');
return (
<Wrapper>
<Helmet>
<title>{`404 ${t('title')}`}</title>
<meta name="description" content={t('title')} />
</Helmet>
<h1>
<SearchOutlined className="icon" />
404
</h1>
<h2>{t('title')}</h2>
</Wrapper>
);
}
Example #5
Source File: Table.stories.tsx From ant-table-extensions with MIT License | 6 votes |
CustomSeachInput = () => {
return (
<Table
dataSource={dataSource}
columns={columns}
searchableProps={{
// dataSource,
// setDataSource: setSearchDataSource,
inputProps: {
placeholder: "Search this table...",
prefix: <SearchOutlined />,
},
}}
/>
);
}
Example #6
Source File: index.tsx From nebula-studio with Apache License 2.0 | 6 votes |
Search = (props: IProps) => {
const { onSearch, type } = props;
const location = useLocation();
const [value, setValue] = useState('');
useEffect(() => {
setValue('');
}, [location.pathname]);
return (
<div className={styles.schemaSearch}>
<Input
value={value}
prefix={<SearchOutlined className={styles.inputSearch} onClick={() => onSearch(value)} />}
allowClear={true}
placeholder={intl.get('common.namePlaceholder', { name: type })}
onChange={e => setValue(e.target.value)}
onPressEnter={() => onSearch(value)}
style={{ width: 300 }}
suffix={null}
/>
</div>
);
}
Example #7
Source File: index.tsx From fe-v5 with Apache License 2.0 | 6 votes |
BaseSearchInput: React.FC<IBaseSearchInputProps> = ({
onSearch,
...props
}) => {
const { t } = useTranslation();
const [value, setValue] = useState<string>('');
return (
<Input
prefix={<SearchOutlined />}
value={value}
onChange={(e) => {
setValue(e.target.value);
if (e.target.value === '') {
onSearch && onSearch('');
}
}}
onPressEnter={(e) => {
onSearch && onSearch(value);
}}
{...props}
/>
);
}
Example #8
Source File: searchBox.tsx From metaplex with Apache License 2.0 | 6 votes |
SearchBox = () => {
return (
<Button
className="search-btn"
shape="circle"
icon={<SearchOutlined />}
></Button>
);
}
Example #9
Source File: index.tsx From visual-layout with MIT License | 6 votes |
Components: React.FC<{}> = () => {
const [value, setValue] = useState('');
const { appService } = useContext(AppContext);
const pageService = appService.project.getCurrentPage();
return useMemo(
() => (
<div className={styles.container}>
<div className={styles.search}>
<components.Input
placeholder="组件搜索"
onChange={e => setValue(e.target.value)}
addonAfter={<SearchOutlined />}
/>
</div>
<div className={styles.components}>
{ComponentsAST.filter(component => {
return (
!value ||
!component.children ||
(typeof component.children !== 'string' &&
component.children.every(child => {
return new RegExp(`${_.escapeRegExp(value)}`, 'ig').test(
isString(child) ? child : child._name,
);
}))
);
}).map((ast, index) => (
<Component key={index} ast={ast} pageService={pageService} />
))}
</div>
</div>
),
[pageService, value],
);
}
Example #10
Source File: Participants.tsx From nanolooker with MIT License | 6 votes |
Progress: React.FC<ProgressProps> = ({ isCompleted, hash }) => {
const { theme } = React.useContext(PreferencesContext);
return isCompleted || (hash && hash !== "0") ? (
<Space size={6}>
<CheckCircleTwoTone
twoToneColor={
theme === Theme.DARK ? TwoToneColors.RECEIVE_DARK : "#52c41a"
}
/>
{hash && hash !== "0" ? (
<Link to={`/block/${hash}`}>
<Button shape="circle" size="small" icon={<SearchOutlined />} />
</Link>
) : null}
</Space>
) : (
<CloseCircleOutlined />
);
}
Example #11
Source File: general-menu.editor.tsx From next-basics with GNU General Public License v3.0 | 6 votes |
export function GeneralMenuEditor({
nodeUid,
editorProps,
}: EditorComponentProps): React.ReactElement {
const node = useBuilderNode<GeneralMenuProperties>({ nodeUid });
return (
<EditorContainer nodeUid={nodeUid}>
<div className={styles.menuContainer}>
{editorProps?.showSearch && (
<div className={styles.search}>
<SearchOutlined className={styles.searchIcon} />
</div>
)}
<div className={styles.title}></div>
{range(0, 3).map((_, index) => (
<div key={index} className={styles.item}>
<div className={styles.icon}></div>
<div className={styles.text}></div>
</div>
))}
</div>
</EditorContainer>
);
}
Example #12
Source File: SearchBox.tsx From posthog-foss with MIT License | 6 votes |
export function SearchBox(): JSX.Element {
const { showPalette } = useActions(commandPaletteLogic)
return (
<div className="SearchBox" onClick={showPalette} data-attr="command-palette-toggle">
<div className="SearchBox__primary-area">
<SearchOutlined className="SearchBox__magnifier" />
Search
</div>
<div className="SearchBox__keyboard-shortcut">{platformCommandControlKey('K')}</div>
</div>
)
}
Example #13
Source File: WorkbenchTree.tsx From next-basics with GNU General Public License v3.0 | 5 votes |
export function WorkbenchTree({
nodes,
placeholder,
searchPlaceholder,
noSearch,
}: WorkbenchTreeProps): ReactElement {
const [q, setQ] = useState<string>(null);
const handleSearchChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
setQ(event.target.value);
},
[]
);
const { matchNode } = useWorkbenchTreeContext();
const trimmedLowerQ = q?.trim().toLowerCase();
const filteredNodes = useMemo(() => {
if (noSearch || !trimmedLowerQ || !nodes) {
return nodes;
}
const walk = (node: WorkbenchNodeData): boolean => {
node.matchedSelf = matchNode(node, trimmedLowerQ);
const hasMatchedChildren = node.children?.map(walk).includes(true);
node.matched = node.matchedSelf || hasMatchedChildren;
return node.matched;
};
nodes.forEach(walk);
return nodes.slice();
}, [noSearch, trimmedLowerQ, nodes, matchNode]);
return nodes?.length ? (
<div>
{!noSearch && (
<div className={styles.searchBox}>
<Input
value={q}
onChange={handleSearchChange}
size="small"
placeholder={searchPlaceholder}
prefix={<SearchOutlined />}
allowClear
/>
</div>
)}
<SearchingContext.Provider value={!!q}>
<TreeList nodes={filteredNodes} level={1} />
</SearchingContext.Provider>
</div>
) : (
<div className={styles.placeholder}>{placeholder}</div>
);
}
Example #14
Source File: PropertyNamesSelect.tsx From posthog-foss with MIT License | 5 votes |
PropertyNamesSearch = (): JSX.Element => {
const { query, filteredProperties, isSelected } = useValues(propertySelectLogic)
const { setQuery, toggleProperty } = useActions(propertySelectLogic)
return (
<>
<Input
onChange={({ target: { value } }) => setQuery(value)}
allowClear
className="search-box"
placeholder="Search for properties"
prefix={<SearchOutlined />}
/>
<div className="search-results">
{filteredProperties.length ? (
filteredProperties.map((property) => (
<Checkbox
key={property.name}
className={'checkbox' + (isSelected(property.name) ? ' checked' : '')}
checked={isSelected(property.name)}
onChange={() => toggleProperty(property.name)}
>
{property.highlightedNameParts.map((part, index) =>
part.toLowerCase() === query.toLowerCase() ? (
<b key={index}>{part}</b>
) : (
<React.Fragment key={index}>{part}</React.Fragment>
)
)}
</Checkbox>
))
) : (
<p className="no-results-message">
No properties match <b>“{query}”</b>. Refine your search to try again.
</p>
)}
</div>
</>
)
}
Example #15
Source File: index.tsx From ant-design-pro-V5-multitab with MIT License | 5 votes |
HeaderSearch: React.FC<HeaderSearchProps> = (props) => {
const {
className,
defaultValue,
onVisibleChange,
placeholder,
visible,
defaultVisible,
...restProps
} = props;
const inputRef = useRef<Input | null>(null);
const [value, setValue] = useMergeValue<string | undefined>(defaultValue, {
value: props.value,
onChange: props.onChange,
});
const [searchMode, setSearchMode] = useMergeValue(defaultVisible ?? false, {
value: props.visible,
onChange: onVisibleChange,
});
const inputClass = classNames(styles.input, {
[styles.show]: searchMode,
});
return (
<div
className={classNames(className, styles.headerSearch)}
onClick={() => {
setSearchMode(true);
if (searchMode && inputRef.current) {
inputRef.current.focus();
}
}}
onTransitionEnd={({ propertyName }) => {
if (propertyName === 'width' && !searchMode) {
if (onVisibleChange) {
onVisibleChange(searchMode);
}
}
}}
>
<SearchOutlined
key="Icon"
style={{
cursor: 'pointer',
}}
/>
<AutoComplete
key="AutoComplete"
className={inputClass}
value={value}
options={restProps.options}
onChange={setValue}
>
<Input
size="small"
ref={inputRef}
defaultValue={defaultValue}
aria-label={placeholder}
placeholder={placeholder}
onKeyDown={(e) => {
if (e.key === 'Enter') {
if (restProps.onSearch) {
restProps.onSearch(value);
}
}
}}
onBlur={() => {
setSearchMode(false);
}}
/>
</AutoComplete>
</div>
);
}
Example #16
Source File: index.tsx From anew-server with MIT License | 5 votes |
HeaderSearch: React.FC<HeaderSearchProps> = (props) => {
const {
className,
defaultValue,
onVisibleChange,
placeholder,
visible,
defaultVisible,
...restProps
} = props;
const inputRef = useRef<Input | null>(null);
const [value, setValue] = useMergedState<string | undefined>(defaultValue, {
value: props.value,
onChange: props.onChange,
});
const [searchMode, setSearchMode] = useMergedState(defaultVisible ?? false, {
value: props.visible,
onChange: onVisibleChange,
});
const inputClass = classNames(styles.input, {
[styles.show]: searchMode,
});
return (
<div
className={classNames(className, styles.headerSearch)}
onClick={() => {
setSearchMode(true);
if (searchMode && inputRef.current) {
inputRef.current.focus();
}
}}
onTransitionEnd={({ propertyName }) => {
if (propertyName === 'width' && !searchMode) {
if (onVisibleChange) {
onVisibleChange(searchMode);
}
}
}}
>
<SearchOutlined
key="Icon"
style={{
cursor: 'pointer',
}}
/>
<AutoComplete
key="AutoComplete"
className={inputClass}
value={value}
options={restProps.options}
onChange={setValue}
>
<Input
size="small"
ref={inputRef}
defaultValue={defaultValue}
aria-label={placeholder}
placeholder={placeholder}
onKeyDown={(e) => {
if (e.key === 'Enter') {
if (restProps.onSearch) {
restProps.onSearch(value);
}
}
}}
onBlur={() => {
setSearchMode(false);
}}
/>
</AutoComplete>
</div>
);
}
Example #17
Source File: Worklist.tsx From slim with Apache License 2.0 | 5 votes |
getColumnSearchProps = (dataIndex: string): object => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }: {
setSelectedKeys: (selectedKeys: React.Key[]) => void
selectedKeys: React.Key[]
confirm: (params?: FilterConfirmProps) => void
clearFilters: () => void
}) => (
<div style={{ padding: 8 }}>
<Input
placeholder='Search'
value={selectedKeys[0]}
onChange={e => setSelectedKeys(
e.target.value !== undefined ? [e.target.value] : []
)}
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
style={{ width: 188, marginBottom: 8, display: 'block' }}
/>
<Space>
<Button
type='primary'
onClick={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
icon={<SearchOutlined />}
size='small'
style={{ width: 90 }}
>
Search
</Button>
<Button
onClick={() => this.handleReset(clearFilters)}
size='small'
style={{ width: 90 }}
>
Reset
</Button>
</Space>
</div>
),
filterIcon: (filtered: boolean) => (
<SearchOutlined
style={{ color: filtered ? '#1890ff' : undefined }}
/>
)
})
Example #18
Source File: SearchBar.tsx From next-basics with GNU General Public License v3.0 | 5 votes |
export function SearchBar(props: SearchBarProps): React.ReactElement {
const [focus, setFocus] = React.useState(false);
const searchInputRef = React.useCallback((element) => {
if (element) {
// Wait for portal mounted first.
Promise.resolve().then(() => {
try {
element.focus();
} catch (e) {
// Do nothing.
}
});
}
}, []);
const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
props.onChange(e.target.value);
};
const handleClick = (e: React.MouseEvent): void => {
e.stopPropagation();
};
const handleKeyDown = (e: React.KeyboardEvent): void => {
const key =
e.key ||
/* istanbul ignore next: compatibility */ e.keyCode ||
/* istanbul ignore next: compatibility */ e.which;
if (
[
"Tab",
"Enter",
"ArrowLeft",
"ArrowUp",
"ArrowRight",
"ArrowDown",
9,
13,
37,
38,
39,
40,
].includes(key)
) {
e.preventDefault();
}
};
const handleFocus = (): void => {
setFocus(true);
};
const handleBlur = (): void => {
setFocus(false);
};
return (
<div
className={classNames(styles.searchBar, {
[styles.focus]: focus,
})}
>
<div className={styles.inputContainer} onClick={handleClick}>
<Input
placeholder={i18next.t(
`${NS_BASIC_BRICKS}:${K.SEARCH_BY_NAME_KEYWORD}`
)}
onChange={handleChange}
onKeyDown={handleKeyDown}
onFocus={handleFocus}
onBlur={handleBlur}
prefix={<SearchOutlined />}
ref={searchInputRef}
/>
</div>
</div>
);
}
Example #19
Source File: index.tsx From fe-v5 with Apache License 2.0 | 5 votes |
index = (_props: any) => {
const history = useHistory();
const { t, i18n } = useTranslation();
const [query, setQuery] = useState('');
const [mine, setMine] = useState(true);
const [days, setDays] = useState(7);
const { curBusiItem } = useSelector<RootState, CommonStoreState>((state) => state.common);
const { tableProps } = useAntdTable((options) => getTableData(options, curBusiItem.id, query, mine, days), { refreshDeps: [curBusiItem.id, query, mine, days] });
const columns: ColumnProps<DataItem>[] = [
{
title: 'ID',
dataIndex: 'id',
width: 100,
},
{
title: t('task.title'),
dataIndex: 'title',
width: 200,
render: (text, record) => {
return <Link to={{ pathname: `/job-tasks/${record.id}/result` }}>{text}</Link>;
},
},
{
title: t('table.operations'),
width: 150,
render: (_text, record) => {
return (
<span>
<Link to={{ pathname: '/job-tasks/add', search: `task=${record.id}` }}>{t('task.clone')}</Link>
<Divider type='vertical' />
<Link to={{ pathname: `/job-tasks/${record.id}/detail` }}>{t('task.meta')}</Link>
</span>
);
},
},
{
title: t('task.creator'),
dataIndex: 'create_by',
width: 100,
},
{
title: t('task.created'),
dataIndex: 'create_at',
width: 160,
render: (text) => {
return moment.unix(text).format('YYYY-MM-DD HH:mm:ss');
},
},
];
return (
<PageLayout
hideCluster
title={
<>
<CodeOutlined />
{t('执行历史')}
</>
}
>
<div style={{ display: 'flex' }}>
<LeftTree></LeftTree>
{curBusiItem.id ? (
<div style={{ flex: 1, padding: 10 }}>
<Row>
<Col span={16} className='mb10'>
<Input
style={{ width: 200, marginRight: 10 }}
prefix={<SearchOutlined />}
defaultValue={query}
onPressEnter={(e) => {
setQuery(e.currentTarget.value);
}}
placeholder='搜索标题'
/>
<Select
style={{ marginRight: 10 }}
value={days}
onChange={(val: number) => {
setDays(val);
}}
>
<Select.Option value={7}>{t('last.7.days')}</Select.Option>
<Select.Option value={15}>{t('last.15.days')}</Select.Option>
<Select.Option value={30}>{t('last.30.days')}</Select.Option>
<Select.Option value={60}>{t('last.60.days')}</Select.Option>
<Select.Option value={90}>{t('last.90.days')}</Select.Option>
</Select>
<Checkbox
checked={mine}
onChange={(e) => {
setMine(e.target.checked);
}}
>
{t('task.only.mine')}
</Checkbox>
</Col>
<Col span={8} style={{ textAlign: 'right' }}>
<Button
type='primary'
ghost
onClick={() => {
history.push('/job-tasks/add');
}}
>
{t('task.temporary.create')}
</Button>
</Col>
</Row>
<Table
rowKey='id'
columns={columns as any}
{...(tableProps as any)}
pagination={
{
...tableProps.pagination,
showSizeChanger: true,
pageSizeOptions: ['10', '50', '100', '500', '1000'],
showTotal: (total) => {
return i18n.language == 'en' ? `Total ${total} items` : `共 ${total} 条`;
},
} as any
}
/>
</div>
) : (
<BlankBusinessPlaceholder text={t('执行历史')}></BlankBusinessPlaceholder>
)}
</div>
</PageLayout>
);
}
Example #20
Source File: Modal.tsx From nanolooker with MIT License | 5 votes |
QRCodeModal = ({ header, account, children }: QRCodeModalProps) => {
const { t } = useTranslation();
const [isVisible, setIsVisible] = React.useState(false);
const { account: accountParam = "" } = useParams<PageParams>();
return (
<>
{React.cloneElement(children, {
onClick: () => {
setIsVisible(true);
},
})}
<Modal
width="300px"
visible={isVisible}
onCancel={() => setIsVisible(false)}
footer={[
<Button
key="submit"
type="primary"
onClick={() => setIsVisible(false)}
>
{t("common.ok")}
</Button>,
]}
>
{header}
<div style={{ textAlign: "center" }}>
<QRCode account={account} />
</div>
<>
{(account === DONATION_ACCOUNT &&
accountParam !== DONATION_ACCOUNT) ||
[
NANOBROWSERQUEST_DONATION_ACCOUNT,
NANOQUAKEJS_DONATION_ACCOUNT,
].includes(account) ? (
<div
style={{
display: "flex",
justifyContent: "center",
marginBottom: "12px",
}}
>
<Copy text={account} />
<Link to={`/account/${account}`} style={{ marginLeft: "12px" }}>
<Button
shape="circle"
size="small"
icon={<SearchOutlined />}
onClick={() => setIsVisible(false)}
/>
</Link>
</div>
) : null}
<Text>{account}</Text>
</>
</Modal>
</>
);
}
Example #21
Source File: index.tsx From dashboard with Apache License 2.0 | 4 votes |
StaffTreeSelectionModal: React.FC<StaffSelectionProps> = (props) => {
const { visible, setVisible, defaultCheckedStaffs, onFinish, allStaffs } = props;
const [staffs, setStaffs] = useState<StaffOption[]>([]);
const [staffNodes, setStaffNodes] = useState<TreeNode[]>([]); // 一维的节点
const [staffTree, setStaffTree] = useState<TreeNode[]>([]); // 多维的树节点
const [selectedStaffs, setSelectedStaffs] = useState<StaffOption[]>(defaultCheckedStaffs || []);
const [keyword, setKeyword] = useState<string>('');
const [checkAll, setCheckAll] = useState<boolean>(false);
const [expandAll, setExpandAll] = useState<boolean>(false);
const [checkedNodeKeys, setCheckedNodeKeys] = useState<string[]>([]);
const [expandedNodeKeys, setExpandedNodeKeys] = useState<string[]>([rootNode]);
const allStaffMap = _.keyBy(allStaffs, 'ext_id');
const onCheckAllChange = (e: CheckboxChangeEvent) => {
let items: StaffOption[];
if (e.target.checked) {
items = _.uniqWith<StaffOption>(
[...staffs, ...selectedStaffs],
(a, b) => a.ext_id === b.ext_id,
);
} else {
// @ts-ignore
items = _.differenceWith<StaffParam>(selectedStaffs, staffs, (a, b) => a.ext_id === b.ext_id);
}
setSelectedStaffs(items);
setCheckAll(e.target.checked);
};
const onNodesCheck = (checked: string[]) => {
const checkedExtStaffIDs: string[] = [];
let selectedExtStaffIDs = _.map(selectedStaffs, 'ext_id');
let checkedKeys = [...checked];
// 找出本次uncheck的key,根据这些key的ext_id去删除相关checkedKey
const uncheckedKeys = _.difference(checkedNodeKeys, checkedKeys);
_.forEach<string>(uncheckedKeys, (key: string) => {
const [, , nodeID] = key.split(separator);
// eslint-disable-next-line no-param-reassign
// @ts-ignore
checkedKeys = checkedKeys.filter<string>((checkedKey) => {
return !checkedKey.includes(`${separator}${nodeID}`);
});
});
// 记录当前所有checked的key
checkedKeys.forEach((key) => {
const [nodeType, , nodeID] = key.split(separator);
if (nodeType === 'node') {
checkedExtStaffIDs.push(nodeID);
selectedExtStaffIDs.push(nodeID);
}
});
// 计算需要删除的extStaffID
// @ts-ignore
const shouldDeleteExtStaffIDs = _.difference(_.map(staffs, 'ext_id'), checkedExtStaffIDs);
selectedExtStaffIDs = _.difference(
_.uniq(selectedExtStaffIDs),
_.uniq(shouldDeleteExtStaffIDs),
);
const items = selectedExtStaffIDs.map((selectedExtStaffID) => {
return allStaffMap[selectedExtStaffID];
});
setCheckAll(staffs.length === items.length);
setSelectedStaffs(items);
};
const nodeRender = (node: DataNode): ReactNode => {
const [nodeType, , extStaffID] = node.key.toString().split(separator);
if (nodeType === 'node') {
const staff = allStaffMap[extStaffID];
if (staff) {
return (
<div className={styles.staffTitleNode}>
<img src={FormatWeWorkAvatar(staff?.avatar_url, 60)} className={styles.avatar} />
<div className={styles.text}>
<span className={styles.title}>{staff?.name}</span>
<em
style={{
color: RoleColorMap[staff?.role_type],
borderColor: RoleColorMap[staff?.role_type],
}}
>
{RoleMap[staff?.role_type] || '普通员工'}
</em>
</div>
</div>
);
}
}
return (
<>
<FolderFilled
style={{
color: '#47a7ff',
fontSize: 20,
marginRight: 6,
verticalAlign: -3,
}}
/>
{node.title}
</>
);
};
useEffect(() => {
setSelectedStaffs(defaultCheckedStaffs || []);
setKeyword('');
}, [defaultCheckedStaffs, visible]);
// 监听选中员工变化,计算checked的树节点
useEffect(() => {
const allStaffNodeKeys = _.map(staffNodes, 'key');
// 计算当前选中的员工,命中的key
const matchedKeys: string[] = [];
allStaffNodeKeys.forEach((key: string) => {
selectedStaffs.forEach((staff) => {
if (key.includes(`${separator}${staff?.ext_id}`)) {
matchedKeys.push(key);
}
});
});
setCheckedNodeKeys(matchedKeys);
}, [selectedStaffs, staffNodes]);
// 关键词变化的时候
useEffect(() => {
const filteredStaffs = allStaffs.filter((item) => {
// 搜索部门名称
let isDepartmentMatch = false;
item?.departments?.forEach((department) => {
if (department.name.includes(keyword)) {
isDepartmentMatch = true;
}
});
return keyword === '' || isDepartmentMatch || item.label.includes(keyword);
});
setStaffs(filteredStaffs);
const { nodes, tree } = buildStaffTree(filteredStaffs);
// 这里同步更新node节点和选中key值
let checkedKeys: string[] = [];
nodes.forEach((node) => {
selectedStaffs.forEach((staff) => {
if (node.nodeKey.includes(`${separator}${staff?.ext_id}`)) {
checkedKeys.push(node.key);
}
});
});
checkedKeys = _.uniq<string>(checkedKeys);
setCheckedNodeKeys(checkedKeys);
setCheckAll(false);
setStaffNodes(nodes);
setStaffTree(tree);
}, [allStaffs, keyword]);
// @ts-ignore
return (
<Modal
width={665}
className={'dialog from-item-label-100w'}
visible={visible}
zIndex={1001}
onCancel={() => setVisible(false)}
onOk={() => {
if (onFinish) {
onFinish(selectedStaffs);
}
setVisible(false);
}}
>
<h2 className='dialog-title'> 选择员工 </h2>
<div className={styles.addStaffDialogContent}>
<div className={styles.container}>
<div className={styles.left}>
<p className={styles.toolTop} style={{ marginBottom: 0 }}>
<Search
className={styles.searchInput}
enterButton={'搜索'}
prefix={<SearchOutlined />}
placeholder='请输入员工昵称'
allowClear
value={keyword}
onChange={(e) => {
setKeyword(e.target.value);
}}
/>
</p>
<p style={{ marginBottom: 0 }}>
<Checkbox checked={checkAll} onChange={onCheckAllChange}>
全部成员({staffs.length}):
</Checkbox>
<Button
type={'link'}
onClick={() => {
const currentStatus = !expandAll;
if (currentStatus) {
setExpandedNodeKeys(
_.map(
staffNodes.filter((staffNode) => staffNode.type === 'group'),
'key',
),
);
} else {
setExpandedNodeKeys([rootNode]);
}
setExpandAll(currentStatus);
}}
style={{ marginRight: 30 }}
>
{!expandAll ? '展开部门' : '收起部门'}
</Button>
</p>
<div className={styles.allStaff}>
{staffTree.length === 0 && <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
<Tree
className={styles.staffTree}
autoExpandParent={true}
checkedKeys={checkedNodeKeys}
defaultExpandedKeys={checkedNodeKeys}
expandedKeys={expandedNodeKeys}
// @ts-ignore
onExpand={(expandedKeys: string[]) => {
setExpandedNodeKeys(expandedKeys);
}}
height={300}
switcherIcon={<CaretDownFilled style={{ color: '#47a7ff' }} />}
checkable={true}
multiple={true}
treeData={staffTree}
// @ts-ignore
onCheck={onNodesCheck}
titleRender={nodeRender}
/>
</div>
</div>
<div className={styles.right}>
<p>
已选成员({selectedStaffs.length}):
<Button
type={'link'}
onClick={() => {
setSelectedStaffs([]);
setCheckAll(false);
}}
>
清空
</Button>
</p>
<ul className={styles.allStaffList}>
{selectedStaffs.map((staff) => {
if (!staff?.ext_id) {
return '';
}
return (
<li
key={staff?.ext_id}
onClick={() => {
setSelectedStaffs(
selectedStaffs.filter((item) => item?.ext_id !== staff?.ext_id),
);
}}
>
<div className={styles.avatarAndName}>
<img src={staff?.avatar_url} className={styles.avatar} />
<div className='flex-col align-left'>
<span>{staff?.name}</span>
</div>
</div>
<CloseOutlined />
</li>
);
})}
</ul>
</div>
</div>
</div>
</Modal>
);
}
Example #22
Source File: index.tsx From datart with Apache License 2.0 | 4 votes |
export function ListTitle({
title,
subTitle,
search,
add,
more,
back,
className,
onSearch,
onPrevious,
onNext,
}: ListTitleProps) {
const [searchbarVisible, setSearchbarVisible] = useState(false);
const t = useI18NPrefix('components.listTitle');
const toggleSearchbar = useCallback(() => {
setSearchbarVisible(!searchbarVisible);
}, [searchbarVisible]);
const moreMenuClick = useCallback(
({ key }) => {
more?.callback(key, onPrevious, onNext);
},
[more, onPrevious, onNext],
);
const backClick = useCallback(() => {
onPrevious && onPrevious();
}, [onPrevious]);
return (
<Wrapper className={className}>
<Title className="title">
{back && (
<span className="back" onClick={backClick}>
<LeftOutlined />
</span>
)}
{title && <h3>{title}</h3>}
{subTitle && <h5>{subTitle}</h5>}
<Space size={SPACE_UNIT}>
{search && (
<Tooltip title={t('search')} placement="bottom">
<ToolbarButton
size="small"
icon={<SearchOutlined />}
color={searchbarVisible ? PRIMARY : void 0}
onClick={toggleSearchbar}
/>
</Tooltip>
)}
{add && <AddButton dataSource={add} />}
{more && (
<Popup
getPopupContainer={triggerNode =>
triggerNode.parentElement as HTMLElement
}
trigger={['click']}
placement="bottomRight"
content={
<MenuWrapper
prefixCls="ant-dropdown-menu"
selectable={false}
onClick={moreMenuClick}
>
{more.items.map(({ key, text, prefix, suffix }) => (
<MenuListItem
key={key}
{...(prefix && { prefix })}
{...(suffix && { suffix })}
>
{text}
</MenuListItem>
))}
</MenuWrapper>
}
>
<ToolbarButton size="small" icon={<MoreOutlined />} />
</Popup>
)}
</Space>
</Title>
<Searchbar visible={searchbarVisible}>
<Input
className="search-input"
prefix={<SearchOutlined className="icon" />}
placeholder={t('searchValue')}
bordered={false}
onChange={onSearch}
/>
</Searchbar>
</Wrapper>
);
}
Example #23
Source File: index.tsx From nanolooker with MIT License | 4 votes |
Search = ({ isHome = false }) => {
const { t } = useTranslation();
const { theme } = React.useContext(PreferencesContext);
const { knownAccounts } = React.useContext(KnownAccountsContext);
const { bookmarks } = React.useContext(BookmarksContext);
const hasAccountBookmarks = !!Object.keys(bookmarks?.account || {}).length;
const [isExpanded, setIsExpanded] = React.useState(isHome);
const [isError, setIsError] = React.useState(false);
const [filteredResults, setFilteredResults] = React.useState([] as any);
const { searchValue, setSearchValue } = useSearch();
const [accountBookmarks, setAccountBookmarks] = React.useState<
{ alias: string; account: string }[]
>([]);
const {
searchHistory,
addSearchHistory,
removeSearchHistory,
} = useSearchHistory();
const searchRef = React.useRef(null);
const [isOpen, setIsOpen] = React.useState(false);
const [invalidQrCode, setInvalidQrCode] = React.useState("");
let history = useHistory();
const validateSearch = React.useCallback(
async (value: any) => {
if (!value) {
setIsError(false);
setFilteredResults([]);
} else {
const isValidAccount = isValidAccountAddress(value);
const isValidBlock = isValidBlockHash(value);
setIsError(!isValidAccount && !isValidBlock && value.length > 30);
if (isValidBlock) {
addSearchHistory(value.toUpperCase());
history.push(`/block/${value.toUpperCase()}`);
} else if (isValidAccount) {
let account = getPrefixedAccount(value);
setSearchValue(account);
addSearchHistory(account);
history.push(`/account/${account}`);
} else {
const filteredKnownAccounts = knownAccounts
.filter(({ alias }) =>
alias.toLowerCase().includes(value.toLowerCase()),
)
.map(item => renderItem(item));
const filteredAccountBookmarks = accountBookmarks
.filter(({ alias }) =>
alias.toLowerCase().includes(value.toLowerCase()),
)
.map(item => renderItem(item as KnownAccount));
setFilteredResults(
filteredAccountBookmarks.concat(filteredKnownAccounts),
);
}
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[addSearchHistory, knownAccounts, accountBookmarks],
);
React.useEffect(() => {
if (hasAccountBookmarks) {
setAccountBookmarks(
Object.entries(bookmarks?.account).map(([account, alias]) => ({
account,
alias,
})),
);
} else {
setAccountBookmarks([]);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hasAccountBookmarks]);
React.useEffect(() => {
validateSearch(searchValue);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchValue]);
React.useEffect(() => {
if (!isOpen) return;
function onScanSuccess(qrMessage: any) {
if (isValidAccountAddress(qrMessage)) {
setIsOpen(false);
setSearchValue(getPrefixedAccount(qrMessage));
document.getElementById("html5-qrcode-scan-stop-btn")?.click();
} else {
setInvalidQrCode(qrMessage);
}
}
const html5QrcodeScanner = new window.Html5QrcodeScanner(
`qrcode-reader-search${isHome ? "-home" : ""}`,
{
fps: 10,
qrbox: 250,
},
);
html5QrcodeScanner.render(onScanSuccess);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isOpen]);
const renderItem = ({ alias, account }: KnownAccount) => ({
value: account,
label: (
<>
<strong style={{ display: "block" }}>{alias}</strong>
{account}
</>
),
});
return (
<>
<AutoComplete
style={{
float: isExpanded ? "right" : "none",
maxWidth: "calc(100vw - 24px)",
width: isExpanded ? "650px" : "100%",
// transitionDelay: `${isExpanded ? 0 : 0.2}s`,
}}
dropdownClassName={`search-autocomplete-dropdown ${
theme === Theme.DARK ? "theme-dark" : ""
}`}
dropdownStyle={{
maxWidth: "calc(100vw - 40px)",
}}
options={filteredResults}
// @ts-ignore
onSelect={validateSearch}
onChange={value => {
setSearchValue(value);
}}
// @ts-ignore
onPaste={e => {
e.preventDefault();
// @ts-ignore
const paste = (e.clipboardData || window.clipboardData).getData(
"text",
);
const account = getAccountAddressFromText(paste);
const hash = getAccountBlockHashFromText(paste);
if (account || hash) {
setSearchValue(account || hash);
} else {
setSearchValue(paste);
}
}}
value={searchValue}
>
<Input
ref={searchRef}
allowClear
suffix={
<>
<CameraOutlined
onClick={() => setIsOpen(true)}
className="search-history-icon"
style={{ padding: "6px", marginRight: "3px" }}
/>
<Dropdown
key="search-history-dropdown"
overlayStyle={{ zIndex: 1050 }}
overlayClassName={theme === Theme.DARK ? "theme-dark" : ""}
overlay={
<Menu>
{!searchHistory.length ? (
<Menu.Item disabled>{t("search.noHistory")}</Menu.Item>
) : (
searchHistory.map(history => (
<Menu.Item
onClick={() => setSearchValue(history)}
key={history}
>
<div
className="color-normal"
style={{
display: "flex",
alignItems: "flex-start",
}}
>
<div>
{isValidAccountAddress(history) ? (
<WalletOutlined />
) : (
<BlockOutlined />
)}
</div>
<div
className="break-word"
style={{ margin: "0 6px", whiteSpace: "normal" }}
>
{history}
</div>
<DeleteButton
onClick={(e: Event) => {
e.stopPropagation();
removeSearchHistory(history);
}}
/>
</div>
</Menu.Item>
))
)}
</Menu>
}
placement="bottomRight"
>
<HistoryOutlined
className="search-history-icon"
style={{ padding: "6px", marginRight: "6px" }}
/>
</Dropdown>
<SearchOutlined />
</>
}
className={`ant-input-search ${isError ? "has-error" : ""}`}
placeholder={t("search.searchBy")}
onFocus={({ target: { value } }) => {
validateSearch(value);
setIsExpanded(true);
}}
onBlur={() => setIsExpanded(isHome || false)}
size={isHome ? "large" : "middle"}
spellCheck={false}
/>
</AutoComplete>
<Modal
title={t("search.scanWallet")}
visible={isOpen}
onCancel={() => setIsOpen(false)}
footer={[
<Button key="back" onClick={() => setIsOpen(false)}>
{t("common.cancel")}
</Button>,
]}
>
{invalidQrCode ? (
<Alert
message={t("pages.nanoquakejs.invalidAccount")}
description={invalidQrCode}
type="error"
showIcon
style={{ marginBottom: 12 }}
/>
) : null}
<div
id={`qrcode-reader-search${isHome ? "-home" : ""}`}
className="qrcode-reader"
></div>
</Modal>
</>
);
}
Example #24
Source File: index.tsx From dashboard with Apache License 2.0 | 4 votes |
GroupChatSelectionModal: React.FC<GroupChatSelectionProps> = (props) => {
const { visible, setVisible, defaultCheckedGroupChats, onFinish, allGroupChats, ...rest } = props;
const [groupChats, setGroupChats] = useState<GroupChatOption[]>([]);
const [selectedGroupChats, setSelectedGroupChats] = useState<GroupChatOption[]>(defaultCheckedGroupChats || []);
const [keyword, setKeyword] = useState<string>('');
const [checkAll, setCheckAll] = useState<boolean>(false);
const onGroupChatCheckChange = (currentGroupChat: GroupChatOption) => {
let items = [...selectedGroupChats];
// 群聊已被选择,则删除
if (selectedGroupChats.find((item) => item.ext_chat_id === currentGroupChat.ext_chat_id)) {
// 取消该群聊
items = items.filter((item) => {
return item.ext_chat_id !== currentGroupChat.ext_chat_id;
});
} else {
// 没有被选择,则添加
items.push(currentGroupChat);
}
setSelectedGroupChats(items);
};
const onCheckAllChange = (e: CheckboxChangeEvent) => {
let items: GroupChatOption[];
if (e.target.checked) {
items = _.uniqWith<GroupChatOption>([...groupChats, ...selectedGroupChats], (a, b) => a.ext_chat_id === b.ext_chat_id);
} else {
// @ts-ignore
items = _.differenceWith<GroupChatParam>(selectedGroupChats, groupChats, (a, b) => a.ext_chat_id === b.ext_chat_id);
}
setSelectedGroupChats(items);
setCheckAll(e.target.checked);
};
useEffect(() => {
setSelectedGroupChats(defaultCheckedGroupChats || []);
setKeyword('');
}, [defaultCheckedGroupChats, visible]);
useEffect(() => {
setGroupChats(
allGroupChats?.filter((item) => {
return keyword === '' || item?.label?.includes(keyword) || item?.owner_name?.includes(keyword);
}),
);
}, [allGroupChats, keyword]);
// 监听待选和选中群聊,计算全选状态
useEffect(() => {
let isCheckAll = true;
const selectedGroupChatIDs = selectedGroupChats.map((selectedGroupChat) => selectedGroupChat.ext_chat_id);
groupChats.forEach((groupChat) => {
if (!selectedGroupChatIDs.includes(groupChat.ext_chat_id)) {
isCheckAll = false;
}
});
setCheckAll(isCheckAll);
}, [groupChats, selectedGroupChats]);
return (
<Modal
{...rest}
width={665}
zIndex={1001}
className={'dialog from-item-label-100w'}
visible={visible}
onCancel={() => setVisible(false)}
onOk={() => {
if (onFinish) {
onFinish(selectedGroupChats);
}
setVisible(false);
}}
>
<h2 className='dialog-title'> 选择群聊 </h2>
<div className={styles.addGroupChatDialogContent}>
<div className={styles.container}>
<div className={styles.left}>
<div className={styles.toolTop}>
<Search
className={styles.searchInput}
enterButton={'搜索'}
prefix={<SearchOutlined />}
placeholder='请输入群聊名称或群主名称'
allowClear
onChange={(e) => {
setKeyword(e.target.value);
}}
/>
<Checkbox checked={checkAll} onChange={onCheckAllChange}>
全部群聊({groupChats.length}):
</Checkbox>
</div>
<div className={styles.allGroupChat}>
{groupChats.length === 0 && <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />}
<ul className={styles.allGroupChatList}>
{groupChats.map((groupChat) => {
return (
<li key={groupChat.ext_chat_id}
onClick={() => {
onGroupChatCheckChange(groupChat);
}}
>
<div className={styles.avatarAndName}>
<img className={styles.avatar} src={groupChatIcon} />
<div className='flex-col align-left'>
<div className={styles.groupName}>{groupChat.name}</div>
<div className={styles.owner}>群主:{groupChat.owner_name}</div>
</div>
</div>
<Checkbox
checked={selectedGroupChats.find((item) => {
return item?.ext_chat_id === groupChat?.ext_chat_id;
}) !== undefined}
/>
</li>
);
})}
</ul>
</div>
</div>
<div className={styles.right}>
<div className={styles.toolBar}>
已选群聊({selectedGroupChats.length}):
<a
onClick={() => {
setSelectedGroupChats([]);
}}
>
清空
</a>
</div>
<ul className={styles.allGroupChatList}>
{selectedGroupChats.map((groupChat) => {
return (
<li key={groupChat.ext_chat_id}
onClick={() => {
setSelectedGroupChats(selectedGroupChats.filter((item) => item.ext_chat_id !== groupChat.ext_chat_id));
}}
>
<div className={styles.avatarAndName}>
<img className={styles.avatar} src={groupChatIcon} />
<div className='flex-col align-left'>
<div className={styles.groupName}>{groupChat.name}</div>
<div className={styles.owner}>群主:{groupChat.owner_name}</div>
</div>
</div>
<div>
<CloseOutlined />
</div>
</li>
);
})}
</ul>
</div>
</div>
</div>
</Modal>
);
}
Example #25
Source File: ruleModal.tsx From fe-v5 with Apache License 2.0 | 4 votes |
ruleModal: React.FC<props> = ({ visible, ruleModalClose, subscribe }) => {
const { t } = useTranslation();
const { curBusiItem } = useSelector<RootState, CommonStoreState>((state) => state.common);
// const { busiGroups } = useSelector<RootState, CommonStoreState>((state) => state.common);
const [busiGroups, setBusiGroups] = useState<{ id: number; name: string }[]>([]);
const [currentStrategyDataAll, setCurrentStrategyDataAll] = useState([]);
const [currentStrategyData, setCurrentStrategyData] = useState([]);
const [bgid, setBgid] = useState(curBusiItem.id);
const [query, setQuery] = useState<string>('');
useEffect(() => {
setBgid(curBusiItem.id);
}, [curBusiItem]);
useEffect(() => {
getAlertRules();
}, [bgid]);
useEffect(() => {
getTeamList('');
}, []);
useEffect(() => {
filterData();
}, [query, currentStrategyDataAll]);
// 获取业务组列表
const getTeamList = (query: string) => {
console.log(111);
let params = {
all: 1,
query,
limit: 200,
};
getBusinessTeamList(params).then((data) => {
setBusiGroups(data.dat || []);
});
};
const debounceFetcher = useCallback(debounce(getTeamList, 400), []);
const getAlertRules = async () => {
const { success, dat } = await getStrategyGroupSubList({ id: bgid });
if (success) {
setCurrentStrategyDataAll(dat || []);
}
};
const bgidChange = (val) => {
setBgid(val);
};
const filterData = () => {
const data = JSON.parse(JSON.stringify(currentStrategyDataAll));
const res = data.filter((item) => {
return item.name.indexOf(query) > -1 || item.append_tags.join(' ').indexOf(query) > -1;
});
setCurrentStrategyData(res || []);
};
const onSearchQuery = (e) => {
let val = e.target.value;
setQuery(val);
};
const columns: ColumnType<strategyItem>[] = [
{
title: t('集群'),
dataIndex: 'cluster',
render: (data) => {
return <div>{data}</div>;
},
},
{
title: t('级别'),
dataIndex: 'severity',
render: (data) => {
return <Tag color={priorityColor[data - 1]}>S{data}</Tag>;
},
},
{
title: t('名称'),
dataIndex: 'name',
render: (data, record) => {
return (
<div
className='table-active-text'
onClick={() => {
// handleClickEdit(record.id);
}}
>
{data}
</div>
);
},
},
{
title: t('告警接收者'),
dataIndex: 'notify_groups_obj',
render: (data, record) => {
return (
(data.length &&
data.map(
(
user: {
nickname: string;
username: string;
} & { name: string },
index: number,
) => {
return <ColorTag text={user.nickname || user.username || user.name} key={index}></ColorTag>;
},
)) || <div></div>
);
},
},
{
title: t('附加标签'),
dataIndex: 'append_tags',
render: (data) => {
const array = data || [];
return (
(array.length &&
array.map((tag: string, index: number) => {
return <ColorTag text={tag} key={index}></ColorTag>;
})) || <div></div>
);
},
},
{
title: t('更新时间'),
dataIndex: 'update_at',
render: (text: string) => dayjs(Number(text) * 1000).format('YYYY-MM-DD HH:mm:ss'),
},
{
title: t('启用'),
dataIndex: 'disabled',
render: (disabled, record) => (
<Switch
checked={disabled === strategyStatus.Enable}
disabled
size='small'
onChange={() => {
const { id, disabled } = record;
// updateAlertRules({
// ids: [id],
// fields: {
// disabled: !disabled ? 1 : 0
// }
// }, curBusiItem.id
// ).then(() => {
// refreshList();
// });
}}
/>
),
},
{
title: t('操作'),
dataIndex: 'operator',
fixed: 'right',
width: 100,
render: (data, record) => {
return (
<div className='table-operator-area'>
<div
className='table-operator-area-normal'
onClick={() => {
handleSubscribe(record);
}}
>
{t('订阅')}
</div>
</div>
);
},
},
];
const handleSubscribe = (record) => {
subscribe(record);
};
const modalClose = () => {
ruleModalClose();
};
return (
<>
<Modal
title={t('订阅告警规则')}
footer=''
forceRender
visible={visible}
onCancel={() => {
modalClose();
}}
width={'80%'}
>
<div>
<Select
style={{ width: '280px' }}
value={bgid}
onChange={bgidChange}
showSearch
optionFilterProp='children'
filterOption={false}
onSearch={(e) => debounceFetcher(e)}
onBlur={() => getTeamList('')}
>
{busiGroups.map((item) => (
<Option value={item.id} key={item.id}>
{item.name}
</Option>
))}
</Select>
<Input style={{ marginLeft: 10, width: '280px' }} onPressEnter={onSearchQuery} prefix={<SearchOutlined />} placeholder={t('规则名称、附加标签')} />
</div>
<div className='rule_modal_table'>
<Table
rowKey='id'
pagination={{
total: currentStrategyData.length,
showQuickJumper: true,
showSizeChanger: true,
showTotal: (total) => {
return `共 ${total} 条数据`;
},
pageSizeOptions: pageSizeOptionsDefault,
defaultPageSize: 30,
}}
dataSource={currentStrategyData}
columns={columns}
/>
</div>
</Modal>
</>
);
}
Example #26
Source File: index.tsx From dashboard with Apache License 2.0 | 4 votes |
DepartmentSelectionModal: React.FC<DepartmentSelectionProps> = (props) => {
const {visible, setVisible, defaultCheckedDepartments, onFinish, allDepartments} = props;
const [departments, setDepartments] = useState<DepartmentOption[]>([]);
const [departmentNodes, setDepartmentNodes] = useState<TreeNode[]>([]); // 一维的节点
const [departmentTree, setDepartmentTree] = useState<TreeNode[]>([]); // 多维的树节点
const [selectedDepartments, setSelectedDepartments] = useState<DepartmentOption[]>(
_.filter<DepartmentOption>(defaultCheckedDepartments) || [],
);
const [keyword, setKeyword] = useState<string>('');
const [checkAll, setCheckAll] = useState<boolean>(false);
const [expandAll, setExpandAll] = useState<boolean>(false);
const [checkedNodeKeys, setCheckedNodeKeys] = useState<string[]>([]);
const [expandedNodeKeys, setExpandedNodeKeys] = useState<string[]>(['1']);
const allDepartmentMap = _.keyBy(allDepartments, 'ext_id');
const onCheckAllChange = (e: CheckboxChangeEvent) => {
let items: DepartmentOption[];
if (e.target.checked) {
items = _.uniqWith<DepartmentOption>(
[...departments, ...selectedDepartments],
(a, b) => a.ext_id === b.ext_id,
);
} else {
items = _.differenceWith(selectedDepartments, departments, (a, b) => a.ext_id === b.ext_id);
}
setSelectedDepartments(items);
setCheckAll(e.target.checked);
};
const onNodesCheck = (checked: { checked: string[]; halfChecked: string[] }) => {
const checkedExtDepartmentIDs: number[] = [];
let selectedExtDepartmentIDs = selectedDepartments.map((item) => item.ext_id);
let checkedKeys = [...checked.checked];
// 找出本次uncheck的key,根据这些key的ext_id去删除相关checkedKey
const uncheckedKeys = _.difference(checkedNodeKeys, checkedKeys);
_.forEach<string>(uncheckedKeys, (key: string) => {
// @ts-ignore
checkedKeys = checkedKeys.filter<string>((checkedKey) => {
return !checkedKey.includes(key);
});
});
// 记录当前所有checked的key
checkedKeys.forEach((key) => {
checkedExtDepartmentIDs.push(Number(key));
selectedExtDepartmentIDs.push(Number(key));
});
// 计算需要删除的extDepartmentID
// @ts-ignore
const shouldDeleteExtDepartmentIDs = _.difference(
_.map(departments, 'ext_id'),
checkedExtDepartmentIDs,
);
selectedExtDepartmentIDs = _.difference(
_.uniq(selectedExtDepartmentIDs),
_.uniq(shouldDeleteExtDepartmentIDs),
);
const items = selectedExtDepartmentIDs.map((selectedExtDepartmentID) => {
return allDepartmentMap[selectedExtDepartmentID];
});
setCheckAll(departments.length === items.length);
setSelectedDepartments(items);
};
const nodeRender = (node: DataNode): ReactNode => {
return (
<>
<FolderFilled
style={{
color: '#47a7ff',
fontSize: 20,
marginRight: 6,
verticalAlign: -6,
}}
/>
{node.title}
</>
);
};
useEffect(() => {
setSelectedDepartments(_.filter<DepartmentOption>(defaultCheckedDepartments) || []);
setKeyword('');
}, [defaultCheckedDepartments, visible]);
// 监听选中部门变化,计算checked的树节点
useEffect(() => {
const allDepartmentNodeKeys = _.map(departmentNodes, 'key');
// 计算当前选中的部门,命中的key
const matchedKeys: string[] = [];
allDepartmentNodeKeys.forEach((key: string) => {
selectedDepartments.forEach((department) => {
if (key === `${department.ext_id}`) {
matchedKeys.push(key);
}
});
});
setCheckedNodeKeys(matchedKeys);
}, [selectedDepartments]);
// 关键词变化的时候
useEffect(() => {
let filteredDepartments: DepartmentOption[] = [];
allDepartments.forEach((item) => {
if (keyword.trim() === '' || item.label.includes(keyword.trim())) {
filteredDepartments.push(item);
}
})
// 把搜索结果的父级节点放进结果集,方便构造树
const pushParentDepartment = (item: DepartmentOption) => {
if (item.ext_parent_id === 0) {
filteredDepartments.push(item);
return;
}
const parent = allDepartmentMap[item.ext_parent_id];
// eslint-disable-next-line @typescript-eslint/no-unused-vars
filteredDepartments.push(parent);
pushParentDepartment(parent);
};
filteredDepartments.forEach((item) => {
pushParentDepartment(item);
})
filteredDepartments = _.uniq<DepartmentOption>(filteredDepartments);
setDepartments(filteredDepartments);
const {nodes, tree} = buildDepartmentTree(filteredDepartments);
// 这里同步更新node节点和选中key值
let checkedKeys: string[] = [];
nodes.forEach((node) => {
selectedDepartments.forEach((department) => {
if (node.key === `${department.ext_id}`) {
checkedKeys.push(node.key);
}
});
});
checkedKeys = _.uniq<string>(checkedKeys);
setCheckedNodeKeys(checkedKeys);
setCheckAll(false);
setDepartmentNodes(nodes);
setDepartmentTree(tree);
}, [allDepartments, keyword]);
// @ts-ignore
return (
<Modal
width={665}
className={'dialog from-item-label-100w'}
visible={visible}
zIndex={1001}
onCancel={() => setVisible(false)}
onOk={() => {
if (onFinish) {
onFinish(selectedDepartments);
}
setVisible(false);
}}
>
<h2 className="dialog-title"> 选择部门 </h2>
<div className={styles.addDepartmentDialogContent}>
<div className={styles.container}>
<div className={styles.left}>
<p className={styles.toolTop} style={{marginBottom: 0}}>
<Search
className={styles.searchInput}
enterButton={'搜索'}
prefix={<SearchOutlined/>}
placeholder="请输入部门名称"
allowClear
value={keyword}
onChange={(e) => {
setKeyword(e.target.value);
}}
/>
</p>
<p style={{marginBottom: 0}}>
<Checkbox checked={checkAll} onChange={onCheckAllChange}>
全部部门({departments.length}):
</Checkbox>
<Button
type={'link'}
onClick={() => {
const currentStatus = !expandAll;
if (currentStatus) {
setExpandedNodeKeys(_.map(departmentNodes, 'key'));
} else {
setExpandedNodeKeys(['0']);
}
setExpandAll(currentStatus);
}}
style={{marginRight: 30}}
>
{!expandAll ? '展开全部' : '收起全部'}
</Button>
</p>
<div className={styles.allDepartment}>
{departmentTree.length === 0 && <Empty image={Empty.PRESENTED_IMAGE_SIMPLE}/>}
<Tree
className={styles.departmentTree}
autoExpandParent={false}
checkStrictly={true}
checkedKeys={checkedNodeKeys}
defaultExpandedKeys={checkedNodeKeys}
expandedKeys={expandedNodeKeys}
// @ts-ignore
onExpand={(expandedKeys: string[]) => {
setExpandedNodeKeys(expandedKeys);
}}
height={300}
switcherIcon={<CaretDownFilled style={{color: '#47a7ff'}}/>}
checkable={true}
multiple={true}
treeData={departmentTree}
// @ts-ignore
onCheck={onNodesCheck}
titleRender={nodeRender}
/>
</div>
</div>
<div className={styles.right}>
<p>
已选部门({selectedDepartments.length}):
<Button
type={'link'}
onClick={() => {
setSelectedDepartments([]);
setCheckAll(false);
}}
>
清空
</Button>
</p>
<ul className={styles.allDepartmentList}>
{selectedDepartments.map((department) => {
if (!department) {
return <></>
}
return (
<li
key={department.ext_id}
onClick={() => {
setSelectedDepartments(
selectedDepartments.filter((item) => item.ext_id !== department.ext_id),
);
}}
>
<div className={styles.avatarAndName}>
<div className="flex-col align-left">
<FolderFilled
style={{
color: '#47a7ff',
fontSize: 20,
marginRight: 6,
verticalAlign: -6,
}}
/>
{department.name}
</div>
</div>
<CloseOutlined/>
</li>
);
})}
</ul>
</div>
</div>
</div>
</Modal>
);
}
Example #27
Source File: MemberTable.tsx From datart with Apache License 2.0 | 4 votes |
MemberTable = memo(
({ loading, dataSource, onAdd, onChange }: MemberTableProps) => {
const [keywords, setKeywords] = useState('');
const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([]);
const t = useI18NPrefix('member.roleDetail');
const tg = useI18NPrefix('global');
const filteredSource = useMemo(
() =>
dataSource.filter(
({ username, email, name }) =>
username.toLowerCase().includes(keywords) ||
email.toLowerCase().includes(keywords) ||
(name && name.toLowerCase().includes(keywords)),
),
[dataSource, keywords],
);
const debouncedSearch = useMemo(() => {
const search = e => {
setKeywords(e.target.value);
};
return debounce(search, DEFAULT_DEBOUNCE_WAIT);
}, []);
const removeMember = useCallback(
id => () => {
onChange(dataSource.filter(d => d.id !== id));
setSelectedRowKeys([]);
},
[dataSource, onChange],
);
const removeSelectedMember = useCallback(() => {
onChange(dataSource.filter(d => !selectedRowKeys.includes(d.id)));
setSelectedRowKeys([]);
}, [dataSource, selectedRowKeys, onChange]);
const columns = useMemo(
() => [
{ dataIndex: 'username', title: t('username') },
{ dataIndex: 'email', title: t('email') },
{ dataIndex: 'name', title: t('name') },
{
title: tg('title.action'),
width: 80,
align: 'center' as const,
render: (_, record) => (
<Action onClick={removeMember(record.id)}>{t('remove')}</Action>
),
},
],
[removeMember, t, tg],
);
return (
<>
<Toolbar>
<Col span={4}>
<Button
type="link"
icon={<PlusOutlined />}
className="btn"
onClick={onAdd}
>
{t('addMember')}
</Button>
</Col>
<Col span={14}>
{selectedRowKeys.length > 0 && (
<Button
type="link"
icon={<DeleteOutlined />}
className="btn"
onClick={removeSelectedMember}
>
{t('deleteAll')}
</Button>
)}
</Col>
<Col span={6}>
<Input
placeholder={t('searchMember')}
prefix={<SearchOutlined className="icon" />}
bordered={false}
onChange={debouncedSearch}
/>
</Col>
</Toolbar>
<Table
rowKey="id"
dataSource={filteredSource}
columns={columns}
loading={loading}
size="small"
rowSelection={{ selectedRowKeys, onChange: setSelectedRowKeys }}
bordered
/>
</>
);
},
)
Example #28
Source File: index.tsx From fe-v5 with Apache License 2.0 | 4 votes |
Shield: React.FC = () => {
const { t } = useTranslation();
const history = useHistory();
const [query, setQuery] = useState<string>('');
const { curBusiItem } = useSelector<RootState, CommonStoreState>((state) => state.common);
const [bgid, setBgid] = useState(undefined);
const [clusters, setClusters] = useState<string[]>([]);
const [currentShieldDataAll, setCurrentShieldDataAll] = useState<Array<subscribeItem>>([]);
const [currentShieldData, setCurrentShieldData] = useState<Array<subscribeItem>>([]);
const [loading, setLoading] = useState<boolean>(false);
const columns: ColumnsType = [
{
title: t('集群'),
dataIndex: 'cluster',
render: (data) => {
return <div>{data}</div>;
},
},
{
title: t('告警规则'),
dataIndex: 'rule_name',
render: (data) => {
return <div>{data}</div>;
},
},
{
title: t('订阅标签'),
dataIndex: 'tags',
render: (text: any) => {
return (
<>
{text
? text.map((tag, index) => {
return tag ? <div key={index}>{`${tag.key} ${tag.func} ${tag.func === 'in' ? tag.value.split(' ').join(', ') : tag.value}`}</div> : null;
})
: ''}
</>
);
},
},
{
title: t('告警接收组'),
dataIndex: 'user_groups',
render: (text: string, record: subscribeItem) => {
return (
<>
{record.user_groups?.map((item) => (
<ColorTag text={item.name} key={item.id}></ColorTag>
))}
</>
);
},
},
{
title: t('编辑人'),
ellipsis: true,
dataIndex: 'update_by',
},
{
title: t('操作'),
width: '128px',
dataIndex: 'operation',
render: (text: undefined, record: subscribeItem) => {
return (
<>
<div className='table-operator-area'>
<div
className='table-operator-area-normal'
style={{
cursor: 'pointer',
display: 'inline-block',
}}
onClick={() => {
curBusiItem?.id && history.push(`/alert-subscribes/edit/${record.id}`);
}}
>
{t('编辑')}
</div>
<div
className='table-operator-area-normal'
style={{
cursor: 'pointer',
display: 'inline-block',
}}
onClick={() => {
curBusiItem?.id && history.push(`/alert-subscribes/edit/${record.id}?mode=clone`);
}}
>
{t('克隆')}
</div>
<div
className='table-operator-area-warning'
style={{
cursor: 'pointer',
display: 'inline-block',
}}
onClick={() => {
confirm({
title: t('确定删除该订阅规则?'),
icon: <ExclamationCircleOutlined />,
onOk: () => {
dismiss(record.id);
},
onCancel() {},
});
}}
>
{t('删除')}
</div>
</div>
</>
);
},
},
];
useEffect(() => {
getList();
}, [curBusiItem]);
useEffect(() => {
filterData();
}, [query, clusters, currentShieldDataAll]);
const dismiss = (id: number) => {
deleteSubscribes({ ids: [id] }, curBusiItem.id).then((res) => {
refreshList();
if (res.err) {
message.success(res.err);
} else {
message.success(t('删除成功'));
}
});
};
const filterData = () => {
const data = JSON.parse(JSON.stringify(currentShieldDataAll));
const res = data.filter((item: subscribeItem) => {
const tagFind = item?.tags?.find((tag) => {
return tag.key.indexOf(query) > -1 || tag.value.indexOf(query) > -1 || tag.func.indexOf(query) > -1;
});
const groupFind = item?.user_groups?.find((item) => {
return item?.name?.indexOf(query) > -1;
});
return (item?.rule_name?.indexOf(query) > -1 || !!tagFind || !!groupFind) && ((clusters && clusters?.indexOf(item.cluster) > -1) || clusters?.length === 0);
});
setCurrentShieldData(res || []);
};
const getList = async () => {
if (curBusiItem.id) {
setLoading(true);
const { success, dat } = await getSubscribeList({ id: curBusiItem.id });
if (success) {
setCurrentShieldDataAll(dat || []);
setLoading(false);
}
}
};
const refreshList = () => {
getList();
};
const onSearchQuery = (e) => {
let val = e.target.value;
setQuery(val);
};
const clusterChange = (data) => {
setClusters(data);
};
const busiChange = (data) => {
setBgid(data);
};
return (
<PageLayout title={t('订阅规则')} icon={<CopyOutlined />} hideCluster>
<div className='shield-content'>
<LeftTree
busiGroup={{
// showNotGroupItem: true,
onChange: busiChange,
}}
></LeftTree>
{curBusiItem?.id ? (
<div className='shield-index'>
<div className='header'>
<div className='header-left'>
<RefreshIcon
className='strategy-table-search-left-refresh'
onClick={() => {
refreshList();
}}
/>
<ColumnSelect onClusterChange={(e) => setClusters(e)} />
<Input onPressEnter={onSearchQuery} className={'searchInput'} prefix={<SearchOutlined />} placeholder={t('搜索规则、标签、接收组')} />
</div>
<div className='header-right'>
<Button
type='primary'
className='add'
ghost
onClick={() => {
history.push('/alert-subscribes/add');
}}
>
{t('新增订阅规则')}
</Button>
</div>
</div>
<Table
rowKey='id'
pagination={{
total: currentShieldData.length,
showQuickJumper: true,
showSizeChanger: true,
showTotal: (total) => {
return `共 ${total} 条数据`;
},
pageSizeOptions: pageSizeOptionsDefault,
defaultPageSize: 30,
}}
loading={loading}
dataSource={currentShieldData}
columns={columns}
/>
</div>
) : (
<BlankBusinessPlaceholder text='订阅规则' />
)}
</div>
</PageLayout>
);
}
Example #29
Source File: MailTagFormItem.tsx From datart with Apache License 2.0 | 4 votes |
MailTagFormItem: FC<MailTagFormItemProps> = ({
value,
onChange,
}) => {
const [dataSource, setDataSource] = useState<IUserInfo[]>([]);
const [keyword, setKeyword] = useState('');
const t = useI18NPrefix(
'main.pages.schedulePage.sidebar.editorPage.emailSettingForm.mailTagFormItem',
);
const emails = useMemo(() => {
return value ? value.split(';').filter(v => !!v) : [];
}, [value]);
const onSearch = useCallback(async keyword => {
if (keyword) {
const res = await searchUserEmails(keyword);
setDataSource(res);
} else {
setDataSource([]);
}
}, []);
const onDebouncedSearch = useMemo(
() => debounce(onSearch, DEFAULT_DEBOUNCE_WAIT),
[onSearch],
);
const onSelectOrRemoveEmail = useCallback(
(email: string) => {
const _emails = [...emails];
const index = _emails.indexOf(email);
if (index > -1) {
_emails.splice(index, 1);
} else {
_emails.push(email);
}
onChange?.(_emails.join(';'));
},
[onChange, emails],
);
useEffect(() => {
setKeyword('');
}, [value]);
const options = useMemo(() => {
const items = dataSource.filter(v => !emails.includes(v?.email));
return items.map(({ id, username, email, avatar }) => (
<Option key={id} value={email}>
<Space>
<Avatar src={''} size="small" icon={<UserOutlined />} />
<span>{username}</span>
<span>{email}</span>
</Space>
</Option>
));
}, [dataSource, emails]);
const appendOptions = useMemo(() => {
const newEmail = keyword as string;
if (
!regexEmail.test(newEmail) ||
~dataSource.findIndex(({ email }) => email === newEmail) < 0
) {
return [];
}
return [
<Option key={newEmail} value={newEmail}>
<Space>
<Avatar size="small" icon={<UserOutlined />} />
<span>{newEmail.split('@')[0]}</span>
<span>{newEmail}</span>
</Space>
</Option>,
];
}, [keyword, dataSource]);
const autoCompleteOptions = useMemo(
() => options.concat(appendOptions),
[appendOptions, options],
);
return (
<>
{emails.map(email => (
<EmailTag
closable
key={email}
color="blue"
onClose={() => onSelectOrRemoveEmail(email)}
>
{email}
</EmailTag>
))}
<AutoComplete
value={keyword}
onChange={setKeyword}
dataSource={autoCompleteOptions}
onSearch={onDebouncedSearch}
onSelect={onSelectOrRemoveEmail}
onBlur={() => onSearch('')}
>
<Input suffix={<SearchOutlined />} placeholder={t('placeholder')} />
</AutoComplete>
</>
);
}