Mirror of https://github.com/roostorg/osprey
github.com/roostorg/osprey
1import * as React from 'react';
2import { Checkbox, Input, Modal, Switch } from 'antd';
3import { CheckboxChangeEvent } from 'antd/lib/checkbox/Checkbox';
4import classNames from 'classnames';
5
6import useApplicationConfigStore from '../../stores/ApplicationConfigStore';
7import useQueryStore from '../../stores/QueryStore';
8import OspreyButton from '../../uikit/OspreyButton';
9import Text, { TextSizes, TextWeights, TextColors } from '../../uikit/Text';
10import FilterIcon from '../../uikit/icons/FilterIcon';
11import ModalFooter from '../common/ModalFooter';
12import CustomFeaturesFilter from './CustomFeaturesFilter';
13
14import styles from './FeatureSelectModal.module.css';
15
16const FeatureSelectModal = () => {
17 const customSummaryFeatures = useQueryStore((state) => state.customSummaryFeatures);
18 const updateCustomSummaryFeatures = useQueryStore((state) => state.updateCustomSummaryFeatures);
19 const knownFeatureCategories = useApplicationConfigStore((state) => state.knownFeatureCategories);
20
21 const [useCustomFeatures, setUseCustomFeatures] = React.useState(customSummaryFeatures != null);
22 const [selectedFeatures, setSelectedFeatures] = React.useState<Set<string>>(
23 customSummaryFeatures != null ? new Set(customSummaryFeatures) : new Set()
24 );
25
26 const [isOpen, setIsOpen] = React.useState(false);
27 const [searchInput, setSearchInput] = React.useState('');
28
29 const sortedKnownFeatureCategoryEntries = React.useMemo(
30 () => Object.entries(knownFeatureCategories).sort(),
31 [knownFeatureCategories]
32 );
33
34 const handleCancel = () => {
35 setUseCustomFeatures(customSummaryFeatures != null);
36 setSelectedFeatures(customSummaryFeatures != null ? new Set(customSummaryFeatures) : new Set());
37 setIsOpen(false);
38 };
39
40 const handleOpenModal = () => {
41 setIsOpen(true);
42 };
43
44 const handleSelectFeature = (e: CheckboxChangeEvent) => {
45 const selectedFeature = e.target.value;
46 const updatedFeatures = new Set([...selectedFeatures]);
47
48 if (selectedFeatures.has(selectedFeature)) {
49 updatedFeatures.delete(selectedFeature);
50 } else {
51 updatedFeatures.add(selectedFeature);
52 }
53 setSelectedFeatures(updatedFeatures);
54 };
55
56 const handleSaveSelectedFeatures = () => {
57 const saveVal = useCustomFeatures ? [...selectedFeatures] : null;
58
59 updateCustomSummaryFeatures(saveVal);
60 setIsOpen(false);
61 };
62
63 const handleToggleSwitch = () => {
64 if (useCustomFeatures) {
65 setSelectedFeatures(new Set());
66 }
67
68 setUseCustomFeatures(!useCustomFeatures);
69 };
70
71 const handleSearchInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
72 setSearchInput(e.currentTarget.value);
73 };
74
75 const getReadableFeatureCategory = (featureCategory: string) => {
76 return featureCategory.substring(0, featureCategory.lastIndexOf('.')).replace(/_|\//g, ' ');
77 };
78
79 const isSetSelected = (feature: string) => {
80 if (!useCustomFeatures) return true;
81
82 return selectedFeatures.has(feature as string);
83 };
84
85 const renderModalTitle = () => {
86 return (
87 <>
88 <Text size={TextSizes.H4}>Set Features</Text>
89 <span className={styles.switchSpan}>
90 <Switch
91 onChange={handleToggleSwitch}
92 className={styles.switch}
93 defaultChecked={false}
94 checked={useCustomFeatures}
95 />
96 <Text size={TextSizes.LARGE} weight={TextWeights.NORMAL} color={TextColors.LIGHT_PRIMARY}>
97 Set Custom Features
98 </Text>
99 </span>
100 <CustomFeaturesFilter
101 selectedCustomFeatures={selectedFeatures}
102 useCustomFeatures={useCustomFeatures}
103 onClearFilters={() => setSelectedFeatures(new Set())}
104 onRemoveSelectedFeature={setSelectedFeatures}
105 />
106 <Input
107 allowClear
108 className={styles.__invalid_search}
109 disabled={!useCustomFeatures}
110 placeholder="Filter feature list"
111 onChange={handleSearchInputChange}
112 addonBefore={<FilterIcon width={12} height={12} />}
113 />
114 </>
115 );
116 };
117
118 const renderFeatureCategory = (category: string, features: string[]) => {
119 const filteredFeatures = features.filter((feature) => feature.toUpperCase().includes(searchInput.toUpperCase()));
120 if (filteredFeatures.length === 0) return null;
121
122 return (
123 <div key={category} className={styles.innerCheckboxGrid}>
124 <Text
125 size={TextSizes.H5}
126 className={classNames(styles.category, {
127 [styles.disabled]: !useCustomFeatures,
128 })}
129 >
130 {getReadableFeatureCategory(category)}
131 </Text>
132 {filteredFeatures.sort().map((feature) => {
133 return (
134 <Checkbox
135 key={feature as string}
136 value={feature}
137 disabled={!useCustomFeatures}
138 onChange={handleSelectFeature}
139 checked={isSetSelected(feature)}
140 className={styles.featureCheckboxRow}
141 >
142 <span className={styles.featureSpan}>{feature}</span>
143 </Checkbox>
144 );
145 })}
146 </div>
147 );
148 };
149
150 return (
151 <>
152 <OspreyButton onClick={handleOpenModal}>Select Summary Features</OspreyButton>
153 <Modal
154 title={renderModalTitle()}
155 className={styles.featureModal}
156 width={526}
157 visible={isOpen}
158 onCancel={handleCancel}
159 footer={<ModalFooter onOK={handleSaveSelectedFeatures} onCancel={handleCancel} />}
160 >
161 <div className={styles.checkboxGrid}>
162 {sortedKnownFeatureCategoryEntries.map(([category, features]) => {
163 return renderFeatureCategory(category, features);
164 })}
165 </div>
166 </Modal>
167 </>
168 );
169};
170
171export default FeatureSelectModal;