Skip to content

Commit a35067f

Browse files
committed
feat(Storage): add usage filter component
1 parent e6fea58 commit a35067f

File tree

7 files changed

+164
-0
lines changed

7 files changed

+164
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
.usage-filter {
2+
&__option {
3+
flex-grow: 1;
4+
5+
&-title {
6+
height: var(--yc-text-body-1-line-height);
7+
8+
font-size: var(--yc-text-body-1-font-size);
9+
line-height: var(--yc-text-body-1-line-height);
10+
}
11+
12+
&-meta {
13+
padding: 0 5px;
14+
position: relative;
15+
border-radius: 3px;
16+
font-size: var(--yc-text-caption-2-font-size);
17+
line-height: var(--yc-text-caption-2-line-height);
18+
}
19+
20+
&-bar {
21+
position: absolute;
22+
left: 0;
23+
top: 0;
24+
bottom: 0;
25+
z-index: -1;
26+
27+
background-color: var(--yc-color-infographics-info-medium);
28+
border-radius: 3px;
29+
}
30+
}
31+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import {useEffect, useMemo, useRef, useState} from 'react';
2+
import cn from 'bem-cn-lite';
3+
4+
import {Select, SelectOption} from '@yandex-cloud/uikit';
5+
6+
import EntityStatus from "../../../components/EntityStatus/EntityStatus";
7+
8+
import {getUsageSeverityForEntityStatus} from '../utils';
9+
10+
import i18n from './i18n';
11+
import './UsageFilter.scss';
12+
13+
interface UsageFilterItem {
14+
threshold: number;
15+
count: number;
16+
}
17+
18+
interface UsageFilterProps {
19+
className?: string;
20+
value?: string[];
21+
groups?: UsageFilterItem[];
22+
onChange?: (value: string[]) => void;
23+
debounce?: number;
24+
disabled?: boolean;
25+
}
26+
27+
const b = cn('usage-filter');
28+
29+
export const UsageFilter = (props: UsageFilterProps) => {
30+
const {
31+
className,
32+
value = [],
33+
groups = [],
34+
onChange,
35+
debounce = 200,
36+
disabled,
37+
} = props;
38+
39+
const [filterValue, setFilterValue] = useState(value);
40+
const timer = useRef<number>();
41+
42+
useEffect(() => {
43+
// sync inner state with external value
44+
setFilterValue((prevValue) => {
45+
if (prevValue.join(',') !== value.join(',')) {
46+
return value;
47+
}
48+
49+
return prevValue;
50+
});
51+
}, [value]);
52+
53+
const options = useMemo(() => groups.map(({threshold, count}) => ({
54+
value: String(threshold),
55+
text: `${threshold}%`,
56+
data: {count}
57+
})), [groups]);
58+
59+
const handleUpdate = (newValue: string[]) => {
60+
setFilterValue(newValue);
61+
62+
window.clearTimeout(timer.current);
63+
timer.current = window.setTimeout(() => {
64+
onChange?.(newValue);
65+
}, debounce);
66+
};
67+
68+
const maxWidth = Math.max(...groups.map(({count}) => count));
69+
70+
const renderOption = ({value, data, text}: SelectOption) => (
71+
<div className={b('option')}>
72+
<EntityStatus
73+
className={b('option-title')}
74+
status={getUsageSeverityForEntityStatus(Number(value))}
75+
name={text}
76+
size="xs"
77+
/>
78+
<div className={b('option-meta')}>
79+
{i18n('groups_count', {count: data.count})}
80+
<div className={b('option-bar')} style={{width: `${data.count / maxWidth * 100}%`}} />
81+
</div>
82+
</div>
83+
);
84+
85+
return (
86+
<Select
87+
className={b(null, className)}
88+
label={i18n('label')}
89+
value={filterValue}
90+
placeholder={i18n('default_value')}
91+
options={options}
92+
multiple
93+
onUpdate={handleUpdate}
94+
renderOption={renderOption}
95+
getOptionHeight={() => 50}
96+
popupWidth={280}
97+
disabled={disabled}
98+
/>
99+
);
100+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"label": "Usage:",
3+
"default_value": "Any",
4+
"groups_count": [
5+
"{{count}} group",
6+
"{{count}} groups",
7+
"{{count}} groups",
8+
"No groups"
9+
]
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {i18n, Lang} from '../../../../utils/i18n';
2+
3+
import en from './en.json';
4+
import ru from './ru.json';
5+
6+
const COMPONENT = 'ydb-usage-filter';
7+
8+
i18n.registerKeyset(Lang.En, COMPONENT, en);
9+
i18n.registerKeyset(Lang.Ru, COMPONENT, ru);
10+
11+
export default i18n.keyset(COMPONENT);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"label": "Использование:",
3+
"default_value": "Любое",
4+
"groups_count": [
5+
"{{count}} группа",
6+
"{{count}} группы",
7+
"{{count}} групп",
8+
"Нет групп"
9+
]
10+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './UsageFilter';

src/containers/Storage/utils/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export const getDegradedSeverity = (group: IStoragePoolGroup) => {
4646
};
4747

4848
export const getUsageSeverity = generateEvaluator(80, 85, ['success', 'warning', 'danger']);
49+
export const getUsageSeverityForEntityStatus = generateEvaluator(80, 85, ['Green', 'Yellow', 'Red']);
4950

5051
export const getUsage = (data: IStoragePoolGroup, step = 1) => {
5152
// if limit is 0, display 0

0 commit comments

Comments
 (0)