• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

DLR-SC / ESID / 18381629627

05 Aug 2025 11:51AM UTC coverage: 52.681%. First build
18381629627

push

github

web-flow
Merge pull request #427 from DLR-SC/fix/filter-name

Fix filter action display on long filter names

473 of 607 branches covered (77.92%)

Branch coverage included in aggregate %.

0 of 18 new or added lines in 2 files covered. (0.0%)

4557 of 8941 relevant lines covered (50.97%)

10.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/components/ScenarioComponents/FilterComponents/ManageGroupDialog.tsx
1
// SPDX-FileCopyrightText: 2024 German Aerospace Center (DLR)
2
// SPDX-License-Identifier: Apache-2.0
3

4
import Close from '@mui/icons-material/Close';
×
5
import GroupAdd from '@mui/icons-material/GroupAdd';
×
6
import Box from '@mui/material/Box';
×
7
import Typography from '@mui/material/Typography';
×
8
import IconButton from '@mui/material/IconButton';
×
9
import Divider from '@mui/material/Divider';
×
10
import Card from '@mui/material/Card';
×
11
import CardActionArea from '@mui/material/CardActionArea';
×
12
import CardContent from '@mui/material/CardContent';
×
13
import Button from '@mui/material/Button';
×
14
import useTheme from '@mui/material/styles/useTheme';
×
15
import React, {useState, useEffect, Dispatch} from 'react';
×
16
import ConfirmDialog from '../../shared/ConfirmDialog';
×
17
import GroupFilterCard from './GroupFilterCard';
×
18
import GroupFilterEditor from './GroupFilterEditor';
×
19
import {useTranslation} from 'react-i18next';
×
20
import {GroupFilter} from 'types/group';
21
import {Localization} from 'types/localization';
22

23
export interface ManageGroupDialogProps {
24
  /** A dictionary of group filters.*/
25
  groupFilters: Record<string, GroupFilter>;
26

27
  /** An array of group category.*/
28
  categories: Array<{id: string; name: string}>;
29

30
  /** An array of group subcategory.*/
31
  groups: Array<{id: string; name: string; category: string}>;
32

33
  /** Callback function, which is called, when the close button is called.*/
34
  onCloseRequest: () => void;
35

36
  /**
37
   * A callback that notifies the parent, if there are currently unsaved changes for this group filter.
38
   * @param unsavedChanges - If the group filter has been modified without saving.
39
   */
40
  unsavedChangesCallback: (unsavedChanges: boolean) => void;
41

42
  /** A function that allows setting the groupFilter state so that if the user adds a filter, the new filter will be visible */
43
  setGroupFilters: Dispatch<Record<string, GroupFilter>>;
44

45
  /** An object containing localization information (translation & number formatting).*/
46
  localization?: Localization;
47
}
48

49
/**
50
 * This dialog provides an editor to create, edit, toggle and delete group filters. It uses a classic master detail view
51
 * with the available filters on the left and the filter configuration on the right.
52
 */
53
export default function ManageGroupDialog({
×
54
  groupFilters,
×
55
  categories,
×
56
  groups,
×
57
  onCloseRequest,
×
58
  unsavedChangesCallback,
×
59
  setGroupFilters,
×
60
  localization = {
×
61
    formatNumber: (value: number) => value.toString(),
×
62
    customLang: 'global',
×
63
  },
×
64
}: ManageGroupDialogProps) {
×
65
  const {t: defaultT} = useTranslation();
×
66
  const {t: customT} = useTranslation(localization.customLang);
×
67
  // The currently selected filter.
68
  const [selectedGroupFilter, setSelectedGroupFilter] = useState<GroupFilter | null>(null);
×
69

70
  // A filter the user might open. It will first be checked, if unsaved changes are present.
71
  const [nextSelectedGroupFilter, setNextSelectedGroupFilter] = useState<GroupFilter | null>(null);
×
72

73
  const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
×
74
  const [unsavedChanges, setUnsavedChanges] = useState(false);
×
75

76
  // This effect ensures that the user doesn't discard unsaved changes without confirming it first.
77
  useEffect(() => {
×
78
    if (nextSelectedGroupFilter && nextSelectedGroupFilter.id !== selectedGroupFilter?.id) {
×
79
      // A new group filter has been selected.
80

81
      if (selectedGroupFilter && unsavedChanges) {
×
82
        // There are unsaved changes. Ask for confirmation first!
83
        setConfirmDialogOpen(true);
×
84
      } else {
×
85
        // Everything is saved. Change the selected filter.
86
        setSelectedGroupFilter(nextSelectedGroupFilter);
×
87
      }
×
88
    } else if (!nextSelectedGroupFilter && !unsavedChanges) {
×
89
      // This case is handled, when the user presses the 'abort' button.
90
      setSelectedGroupFilter(null);
×
91
    }
×
92
    unsavedChangesCallback(unsavedChanges);
×
93
  }, [unsavedChanges, nextSelectedGroupFilter, selectedGroupFilter, unsavedChangesCallback, onCloseRequest]);
×
94

95
  const theme = useTheme();
×
96
  return (
×
97
    <Box
×
98
      sx={{
×
99
        display: 'flex',
×
100
        flexDirection: 'column',
×
101
        flexGrow: '1',
×
102
        padding: '26px',
×
103
        alignItems: 'center',
×
104
      }}
×
105
    >
106
      <Box
×
107
        id='group-filter-dialog-title-bar'
×
108
        sx={{
×
109
          display: 'grid',
×
110
          gridTemplateColumns: '1fr auto 1fr',
×
111
          gridColumnGap: '5px',
×
112
          alignItems: 'center',
×
113
          justifyItems: 'center',
×
114
          width: '100%',
×
115
          marginBottom: 2,
×
116
        }}
×
117
      >
118
        <div />
×
119
        <Typography variant='h1'>
×
120
          {localization.overrides && localization.overrides['group-filters.title']
×
121
            ? customT(localization.overrides['group-filters.title'])
×
122
            : defaultT('group-filters.title')}
×
123
        </Typography>
×
124
        <IconButton color='primary' sx={{marginLeft: 'auto'}} onClick={onCloseRequest}>
×
125
          <Close />
×
126
        </IconButton>
×
127
      </Box>
×
128
      <Divider orientation='horizontal' variant='middle' flexItem />
×
129
      <Box
×
130
        sx={{
×
131
          display: 'flex',
×
132
          flexDirection: 'row',
×
133
          flexGrow: '1',
×
134
          width: '100%',
×
135
        }}
×
136
      >
137
        <Box
×
138
          sx={{
×
139
            minWidth: '300px',
×
140
            padding: 2,
×
141
            marginRight: 3,
×
142
            marginTop: 3,
×
143
            display: 'flex',
×
144
            flexDirection: 'column',
×
145
            gap: 1,
×
146
          }}
×
147
        >
148
          {Object.values(groupFilters || {})?.map((item) => (
×
149
            <GroupFilterCard
×
150
              key={item.id}
×
151
              item={item}
×
152
              toggleGroupFilter={(value) => {
×
153
                setGroupFilters({...groupFilters, [item.id]: value});
×
154
              }}
×
155
              deleteGroupFilter={(value) => {
×
156
                const newGroupFilters = {...groupFilters};
×
157
                delete newGroupFilters[value];
×
158
                setGroupFilters(newGroupFilters);
×
159
              }}
×
160
              selected={selectedGroupFilter?.id === item.id}
×
161
              selectFilterCallback={(groupFilter) => setNextSelectedGroupFilter(groupFilter)}
×
162
              localization={localization}
×
163
            />
×
164
          ))}
×
165
          <Card
×
166
            id='group-filter-add-card'
×
167
            variant='outlined'
×
168
            sx={{
×
169
              display: 'flex',
×
170
              flexDirection: 'row',
×
171
              alignItems: 'center',
×
172
              borderColor: theme.palette.primary.main,
×
173
            }}
×
174
          >
175
            <CardActionArea
×
176
              aria-label={
×
177
                localization.overrides && localization.overrides['group-filters.add-group']
×
178
                  ? customT(localization.overrides['group-filters.add-group'])
×
179
                  : defaultT('group-filters.add-group')
×
180
              }
181
              onClick={() => {
×
182
                const categoryFilters: Record<string, Array<string>> = {};
×
183
                categories.forEach((group) => (categoryFilters[group.id] = []));
×
184
                setNextSelectedGroupFilter({
×
185
                  id: crypto.randomUUID(),
×
186
                  name: '',
×
187
                  isVisible: false,
×
188
                  groups: categoryFilters,
×
189
                });
×
190
              }}
×
191
            >
192
              <CardContent
×
193
                sx={{
×
194
                  display: 'flex',
×
195
                  flexDirection: 'row',
×
196
                  alignItems: 'center',
×
197
                  justifyContent: 'space-between',
×
198
                }}
×
199
              >
200
                <Typography variant='button' sx={{color: '#543CF0'}}>
×
201
                  {localization.overrides && localization.overrides['group-filters.add-group']
×
202
                    ? customT(localization.overrides['group-filters.add-group'])
×
203
                    : defaultT('group-filters.add-group')}
×
204
                </Typography>
×
205
                <GroupAdd color='primary' />
×
206
              </CardContent>
×
207
            </CardActionArea>
×
208
          </Card>
×
209
        </Box>
×
210
        <Divider orientation='vertical' flexItem />
×
211
        {selectedGroupFilter ? (
×
212
          <GroupFilterEditor
×
213
            key={selectedGroupFilter.id}
×
214
            groupFilter={selectedGroupFilter}
×
215
            groupFilters={groupFilters}
×
216
            setGroupFilters={setGroupFilters}
×
217
            selectGroupFilterCallback={(groupFilter: GroupFilter | null) => setNextSelectedGroupFilter(groupFilter)}
×
218
            unsavedChangesCallback={(edited) => setUnsavedChanges(edited)}
×
219
            categories={categories}
×
220
            groups={groups}
×
221
            localization={localization}
×
222
          />
×
223
        ) : (
224
          <Box
×
225
            sx={{
×
226
              display: 'flex',
×
227
              flexGrow: '1',
×
228
              flexDirection: 'column',
×
229
              justifyContent: 'center',
×
230
              alignItems: 'center',
×
NEW
231
              padding: 4,
×
232
            }}
×
233
          >
234
            <Typography variant='body1'>
×
235
              {localization.overrides && localization.overrides['group-filters.nothing-selected']
×
236
                ? customT(localization.overrides['group-filters.nothing-selected'])
×
237
                : defaultT('group-filters.nothing-selected')}
×
238
            </Typography>
×
239
            <Button
×
240
              variant='outlined'
×
241
              aria-label={
×
242
                localization.overrides && localization.overrides['group-filters.add-group']
×
243
                  ? customT(localization.overrides['group-filters.add-group'])
×
244
                  : defaultT('group-filters.add-group')
×
245
              }
246
              sx={{marginTop: 2}}
×
247
              onClick={() => {
×
248
                const categoryFilters: Record<string, Array<string>> = {};
×
249
                categories.forEach((group) => (categoryFilters[group.id] = []));
×
250
                setNextSelectedGroupFilter({
×
251
                  id: crypto.randomUUID(),
×
252
                  name: '',
×
253
                  isVisible: false,
×
254
                  groups: categoryFilters,
×
255
                });
×
256
              }}
×
257
            >
258
              <GroupAdd color='primary' />
×
259
            </Button>
×
260
          </Box>
×
261
        )}
262
      </Box>
×
263
      <ConfirmDialog
×
264
        open={confirmDialogOpen}
×
265
        title={
×
266
          localization.overrides && localization.overrides['group-filters.confirm-discard-title']
×
267
            ? customT(localization.overrides['group-filters.confirm-discard-title'])
×
268
            : defaultT('group-filters.confirm-discard-title')
×
269
        }
270
        text={
×
271
          localization.overrides && localization.overrides['group-filters.confirm-discard-text']
×
272
            ? customT(localization.overrides['group-filters.confirm-discard-text'])
×
273
            : defaultT('group-filters.confirm-discard-text')
×
274
        }
275
        abortButtonText={
×
276
          localization.overrides && localization.overrides['group-filters.close']
×
277
            ? customT(localization.overrides['group-filters.close'])
×
278
            : defaultT('group-filters.close')
×
279
        }
280
        confirmButtonText={
×
281
          localization.overrides && localization.overrides['group-filters.discard']
×
282
            ? customT(localization.overrides['group-filters.discard'])
×
283
            : defaultT('group-filters.discard')
×
284
        }
285
        onAnswer={(answer) => {
×
286
          if (answer) {
×
287
            setSelectedGroupFilter(nextSelectedGroupFilter);
×
288
          } else {
×
289
            setNextSelectedGroupFilter(null);
×
290
          }
×
291
          setConfirmDialogOpen(false);
×
292
        }}
×
293
      />
×
294
    </Box>
×
295
  );
296
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc