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

DLR-SC / ESID / 15442352070

04 Jun 2025 12:31PM UTC coverage: 51.036% (+0.3%) from 50.694%
15442352070

Pull #409

github

kunkoala
:bug: fixed variable name for card hiding after merging
Pull Request #409: Added cards drag and drop feature

399 of 512 branches covered (77.93%)

Branch coverage included in aggregate %.

141 of 157 new or added lines in 5 files covered. (89.81%)

1 existing line in 1 file now uncovered.

3837 of 7788 relevant lines covered (49.27%)

4.68 hits per line

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

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

4
import DataCard from './DataCard';
1✔
5
import React, {Dispatch, useMemo} from 'react';
1✔
6
import Box from '@mui/material/Box/Box';
1✔
7
import {FilterValues} from 'types/card';
8
import {GroupFilter} from 'types/group';
9
import {Localization} from 'types/localization';
10
import {DndContext, closestCenter, KeyboardSensor, PointerSensor, useSensor, useSensors} from '@dnd-kit/core';
1✔
11
import {
1✔
12
  arrayMove,
13
  SortableContext,
14
  sortableKeyboardCoordinates,
15
  horizontalListSortingStrategy,
16
} from '@dnd-kit/sortable';
17
import {restrictToHorizontalAxis} from '@dnd-kit/modifiers';
1✔
18
import type {DragEndEvent} from '@dnd-kit/core';
19

20
interface CardContainerProps {
21
  /** A boolean indicating whether the compartments are expanded. */
22
  compartmentsExpanded: boolean;
23

24
  /** A dictionary of card values. Each value is an object containing 'startValues', a dictionary used for rate calculation, and 'compartmentValues' for each card.
25
   *'startValues' help determine whether the values have increased, decreased, or remained the same. */
26
  cardValues: Record<string, Record<string, number | null>> | undefined;
27

28
  referenceValues: Record<string, number> | undefined;
29

30
  /** A dictionary of filter values. This is an array of objects, each containing a title and a dictionary of numbers representing
31
   * the filtered information to be displayed, it's used a dictionary because each card has to have the same amount of filter. */
32
  filterValues?: Record<string, FilterValues[]> | null;
33

34
  /** The compartment that is currently selected. */
35
  selectedCompartmentId: string | null;
36

37
  /** An array of scenarios. */
38
  scenarios: Array<{id: string; name: string; color: string; active: boolean}>;
39

40
  /** A function to set the active scenarios. */
41
  setActiveScenario: Dispatch<{id: string; state: boolean}>;
42

43
  /** The selected scenario. */
44
  selectedScenario: string | null;
45

46
  /** A function to set the selected scenario. */
47
  setSelectedScenario: Dispatch<{id: string; state: boolean}>;
48

49
  /** A function to remove the scenario. */
50
  removeScenario: Dispatch<string>;
51

52
  /** The minimum number of compartment rows. */
53
  minCompartmentsRows: number;
54

55
  /** The maximum number of compartment rows. */
56
  maxCompartmentsRows: number;
57

58
  /** An object containing localization information (translation & number formattation). */
59
  localization?: Localization;
60

61
  /** A dictionary of group filters. */
62
  groupFilters: Record<string, GroupFilter> | undefined;
63

64
  /** A function to order the scenarios. */
65
  orderScenarios: Dispatch<string[]>;
66

67
  /** Boolean to determine if the arrow is displayed */
68
  arrow?: boolean;
69
}
70

71
/**
72
 * This component renders a container for data cards. Each card represents a scenario and contains a title, a list of
73
 * compartment values, and change rates relative to the simulation start.
74
 */
75
export default function CardContainer({
3✔
76
  compartmentsExpanded,
3✔
77
  filterValues,
3✔
78
  selectedCompartmentId,
3✔
79
  scenarios,
3✔
80
  cardValues,
3✔
81
  referenceValues,
3✔
82
  minCompartmentsRows,
3✔
83
  maxCompartmentsRows,
3✔
84
  setActiveScenario,
3✔
85
  removeScenario,
3✔
86
  localization = {
3✔
87
    formatNumber: (value: number) => value.toString(),
3✔
88
    customLang: 'global',
3✔
89
    overrides: {},
3✔
90
  },
3✔
91
  selectedScenario,
3✔
92
  setSelectedScenario,
3✔
93
  groupFilters,
3✔
94
  arrow = true,
3✔
95
  orderScenarios,
3✔
96
}: CardContainerProps) {
3✔
97
  // DnD sensors for initiating DnD events
98
  const sensors = useSensors(
3✔
99
    useSensor(PointerSensor),
3✔
100
    useSensor(KeyboardSensor, {
3✔
101
      coordinateGetter: sortableKeyboardCoordinates,
3✔
102
    })
3✔
103
  );
3✔
104

105
  // Handle drag end
106
  const handleDragEnd = (event: DragEndEvent) => {
3✔
NEW
107
    const {active, over} = event;
×
NEW
108
    if (active.id !== over?.id && over) {
×
NEW
109
      const oldIndex = scenarios.findIndex((s) => s.id === active.id);
×
NEW
110
      const newIndex = scenarios.findIndex((s) => s.id === over.id);
×
111

112
      // Start with the current order
NEW
113
      let ids = scenarios.map((s) => s.id);
×
114
      // Move the dragged item
NEW
115
      ids = arrayMove(ids, oldIndex, newIndex);
×
116

NEW
117
      orderScenarios(ids);
×
NEW
118
    }
×
NEW
119
  };
×
120

121
  const minHeight = useMemo(() => {
3✔
122
    let height;
3✔
123
    if (compartmentsExpanded) {
3✔
124
      if (maxCompartmentsRows > 5) {
3!
125
        height = (390 / 6) * maxCompartmentsRows;
×
126
      } else {
3✔
127
        height = (660 / 6) * maxCompartmentsRows;
3✔
128
      }
3✔
129
    } else {
3!
130
      if (minCompartmentsRows < 4) {
×
131
        height = (480 / 4) * minCompartmentsRows;
×
132
      } else {
×
133
        height = (325 / 4) * minCompartmentsRows;
×
134
      }
×
135
    }
×
136
    return `${height}px`;
3✔
137
  }, [compartmentsExpanded, maxCompartmentsRows, minCompartmentsRows]);
3✔
138

139
  const dataCards = scenarios.map((scenario, index) => (
3✔
140
    <DataCard
9✔
141
      key={scenario.id}
9✔
142
      id={scenario.id}
9✔
143
      color={scenario.color}
9✔
144
      title={scenario.name}
9✔
145
      compartmentsExpanded={compartmentsExpanded}
9✔
146
      compartmentValues={cardValues ? cardValues[scenario.id] : null}
9!
147
      referenceValues={referenceValues ?? null}
9✔
148
      selectedCompartmentId={selectedCompartmentId}
9✔
149
      filterValues={filterValues}
9✔
150
      isSelected={selectedScenario === scenario.id}
9✔
151
      isActive={scenario.active}
9✔
152
      setSelected={setSelectedScenario}
9✔
153
      setActive={setActiveScenario}
9✔
154
      draggable={index !== 0 && index !== 1}
9✔
155
      remove={removeScenario}
9✔
156
      minCompartmentsRows={minCompartmentsRows}
9✔
157
      maxCompartmentsRows={maxCompartmentsRows}
9✔
158
      localization={localization}
9✔
159
      groupFilters={groupFilters}
9✔
160
      arrow={arrow}
9✔
161
    />
9✔
162
  ));
3✔
163

164
  return (
3✔
165
    <DndContext
3✔
166
      sensors={sensors}
3✔
167
      collisionDetection={closestCenter}
3✔
168
      onDragEnd={handleDragEnd}
3✔
169
      modifiers={[restrictToHorizontalAxis]}
3✔
170
    >
171
      <SortableContext items={scenarios.map((s) => s.id)} strategy={horizontalListSortingStrategy}>
3✔
172
        <Box
3✔
173
          id='card-container'
3✔
174
          sx={{
3✔
175
            display: 'flex',
3✔
176
            flexDirection: 'row',
3✔
177
            gap: 4,
3✔
178
            minHeight: minHeight,
3✔
179
            overflowX: 'auto',
3✔
180
            minWidth: 400,
3✔
181
            paddingLeft: 4,
3✔
182
            paddingRight: 4,
3✔
183
            paddingTop: 2,
3✔
184
          }}
3✔
185
        >
186
          {dataCards}
3✔
187
        </Box>
3✔
188
      </SortableContext>
3✔
189
    </DndContext>
3✔
190
  );
191
}
3✔
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