Skip to content

Commit 858c820

Browse files
authored
feat: Create MCP - Components Selection (#160)
1 parent b6f621a commit 858c820

15 files changed

+511
-51
lines changed

public/locales/en.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@
272272
"atLeastOneUser": "You need to have at least one member assigned."
273273
},
274274
"common": {
275+
"documentation": "Documentation",
275276
"close": "Close",
276277
"cannotLoadData": "Cannot load data",
277278
"metadata": "Metadata",
@@ -281,7 +282,10 @@
281282
"region": "Region",
282283
"success": "Success",
283284
"displayName": "Display Name",
284-
"name": "Name"
285+
"name": "Name",
286+
"componentSelection": "Component Selection",
287+
"search": "Search",
288+
"components": "Components"
285289
},
286290
"buttons": {
287291
"viewResource": "View resource",
@@ -300,5 +304,10 @@
300304
"dialogTitle": "Create Managed Control Plane",
301305
"titleText": "Managed Control Plane Created Successfully!",
302306
"subtitleText": "Your Managed Control Plane is being set up. It will be ready to use in just a few minutes. You can safely close this window."
307+
},
308+
"componentsSelection": {
309+
"selectComponents": "Select Components",
310+
"selectedComponents": "Selected Components",
311+
"pleaseSelectComponents": "Choose the components you want to add to your Managed Control Plane."
303312
}
304313
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
.row {
2+
padding: 1rem;
3+
background: var(--sapBackgroundColor);
4+
border-bottom: 1px solid var(--sapList_BorderColor);
5+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import React, { useState } from 'react';
2+
import {
3+
CheckBox,
4+
Select,
5+
Option,
6+
FlexBox,
7+
Title,
8+
Text,
9+
Input,
10+
Button,
11+
Grid,
12+
List,
13+
ListItemStandard,
14+
Icon,
15+
Ui5CustomEvent,
16+
CheckBoxDomRef,
17+
SelectDomRef,
18+
InputDomRef,
19+
} from '@ui5/webcomponents-react';
20+
import styles from './ComponentsSelection.module.css';
21+
import { Infobox } from '../Ui/Infobox/Infobox.tsx';
22+
import { useTranslation } from 'react-i18next';
23+
import { ComponentSelectionItem } from '../../lib/api/types/crate/createManagedControlPlane.ts';
24+
25+
export interface ComponentsSelectionProps {
26+
components: ComponentSelectionItem[];
27+
setSelectedComponents: React.Dispatch<
28+
React.SetStateAction<ComponentSelectionItem[]>
29+
>;
30+
}
31+
32+
export const ComponentsSelection: React.FC<ComponentsSelectionProps> = ({
33+
components,
34+
setSelectedComponents,
35+
}) => {
36+
const [searchTerm, setSearchTerm] = useState('');
37+
const { t } = useTranslation();
38+
const handleSelectionChange = (
39+
e: Ui5CustomEvent<CheckBoxDomRef, { checked: boolean }>,
40+
) => {
41+
const id = e.target?.id;
42+
setSelectedComponents((prev) =>
43+
prev.map((component) =>
44+
component.name === id
45+
? { ...component, isSelected: !component.isSelected }
46+
: component,
47+
),
48+
);
49+
};
50+
51+
const handleSearch = (e: Ui5CustomEvent<InputDomRef, never>) => {
52+
setSearchTerm(e.target.value.trim());
53+
};
54+
55+
const handleVersionChange = (
56+
e: Ui5CustomEvent<SelectDomRef, { selectedOption: HTMLElement }>,
57+
) => {
58+
const selectedOption = e.detail.selectedOption as HTMLElement;
59+
const name = selectedOption.dataset.name;
60+
const version = selectedOption.dataset.version;
61+
setSelectedComponents((prev) =>
62+
prev.map((component) =>
63+
component.name === name
64+
? { ...component, selectedVersion: version || '' }
65+
: component,
66+
),
67+
);
68+
};
69+
70+
const filteredComponents = components.filter(({ name }) =>
71+
name.includes(searchTerm),
72+
);
73+
const selectedComponents = components.filter(
74+
(component) => component.isSelected,
75+
);
76+
77+
return (
78+
<div>
79+
<Title>{t('componentsSelection.selectComponents')}</Title>
80+
81+
<Input
82+
placeholder={t('common.search')}
83+
id="search"
84+
showClearIcon
85+
icon={<Icon name="search" />}
86+
onInput={handleSearch}
87+
/>
88+
89+
<Grid>
90+
<div data-layout-span="XL8 L8 M8 S8">
91+
{filteredComponents.map((component) => (
92+
<FlexBox
93+
key={component.name}
94+
className={styles.row}
95+
gap={10}
96+
justifyContent="SpaceBetween"
97+
>
98+
<CheckBox
99+
valueState="None"
100+
text={component.name}
101+
id={component.name}
102+
checked={component.isSelected}
103+
onChange={handleSelectionChange}
104+
/>
105+
<FlexBox
106+
gap={10}
107+
justifyContent="SpaceBetween"
108+
alignItems="Baseline"
109+
>
110+
{/*This button will be implemented later*/}
111+
{component.documentationUrl && (
112+
<Button design="Transparent">
113+
{t('common.documentation')}
114+
</Button>
115+
)}
116+
<Select
117+
value={component.selectedVersion}
118+
onChange={handleVersionChange}
119+
>
120+
{component.versions.map((version) => (
121+
<Option
122+
key={version}
123+
data-version={version}
124+
data-name={component.name}
125+
selected={component.selectedVersion === version}
126+
>
127+
{version}
128+
</Option>
129+
))}
130+
</Select>
131+
</FlexBox>
132+
</FlexBox>
133+
))}
134+
</div>
135+
<div data-layout-span="XL4 L4 M4 S4">
136+
{selectedComponents.length > 0 ? (
137+
<List headerText={t('componentsSelection.selectedComponents')}>
138+
{selectedComponents.map((component) => (
139+
<ListItemStandard
140+
key={component.name}
141+
text={component.name}
142+
additionalText={component.selectedVersion}
143+
/>
144+
))}
145+
</List>
146+
) : (
147+
<Infobox fullWidth variant={'success'}>
148+
<Text>{t('componentsSelection.pleaseSelectComponents')}</Text>
149+
</Infobox>
150+
)}
151+
</div>
152+
</Grid>
153+
</div>
154+
);
155+
};
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import React, { useEffect, useState } from 'react';
2+
import { ComponentsSelection } from './ComponentsSelection.tsx';
3+
4+
import IllustratedError from '../Shared/IllustratedError.tsx';
5+
import { sortVersions } from '../../utils/componentsVersions.ts';
6+
7+
import { ListManagedComponents } from '../../lib/api/types/crate/listManagedComponents.ts';
8+
import useApiResource from '../../lib/api/useApiResource.ts';
9+
import Loading from '../Shared/Loading.tsx';
10+
import { ComponentSelectionItem } from '../../lib/api/types/crate/createManagedControlPlane.ts';
11+
12+
export interface ComponentItem {
13+
name: string;
14+
versions: string[];
15+
}
16+
17+
export interface ComponentsSelectionProps {
18+
selectedComponents: ComponentSelectionItem[];
19+
setSelectedComponents: React.Dispatch<
20+
React.SetStateAction<ComponentSelectionItem[]>
21+
>;
22+
}
23+
export const ComponentsSelectionContainer: React.FC<
24+
ComponentsSelectionProps
25+
> = ({ setSelectedComponents, selectedComponents }) => {
26+
const {
27+
data: allManagedComponents,
28+
error,
29+
isLoading,
30+
} = useApiResource(ListManagedComponents());
31+
const [isReady, setIsReady] = useState(false);
32+
useEffect(() => {
33+
if (
34+
allManagedComponents?.items.length === 0 ||
35+
!allManagedComponents?.items ||
36+
isReady
37+
)
38+
return;
39+
40+
setSelectedComponents(
41+
allManagedComponents?.items?.map((item) => {
42+
const versions = sortVersions(item.status.versions);
43+
return {
44+
name: item.metadata.name,
45+
versions: versions,
46+
selectedVersion: versions[0],
47+
isSelected: false,
48+
documentationUrl: '',
49+
};
50+
}) ?? [],
51+
);
52+
setIsReady(true);
53+
}, [allManagedComponents, isReady, setSelectedComponents]);
54+
if (isLoading) {
55+
return <Loading />;
56+
}
57+
if (error) return <IllustratedError />;
58+
return (
59+
<>
60+
{selectedComponents.length > 0 ? (
61+
<ComponentsSelection
62+
components={selectedComponents}
63+
setSelectedComponents={setSelectedComponents}
64+
/>
65+
) : (
66+
<IllustratedError title={'Cannot load components list'} />
67+
)}
68+
</>
69+
);
70+
};

src/components/ControlPlanes/List/ControlPlaneListAllWorkspaces.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export default function ControlPlaneListAllWorkspaces({ projectName }: Props) {
4848
window.open(workspaceCreationGuide, '_blank');
4949
}}
5050
>
51-
Help
51+
{t('IllustratedBanner.helpButton')}
5252
</Button>
5353
</FlexBox>
5454
) : (

src/components/Members/MemberRoleSelect.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from '@ui5/webcomponents-react';
1313
import { SelectChangeEventDetail } from '@ui5/webcomponents/dist/Select.js';
1414
import { useEffect, useRef } from 'react';
15+
import { useTranslation } from 'react-i18next';
1516

1617
interface MemberRoleSelectProps {
1718
value: MemberRoles;
@@ -36,10 +37,12 @@ export function MemberRoleSelect({
3637
return;
3738
}
3839
}, [value]);
39-
40+
const { t } = useTranslation();
4041
return (
4142
<FlexBox direction={'Column'}>
42-
<Label for={'member-role-select'}>Role</Label>
43+
<Label for={'member-role-select'}>
44+
{t('MemberTable.columnRoleHeader')}
45+
</Label>
4346
<Select
4447
ref={ref}
4548
id="member-role-select"

src/components/Ui/Infobox/Infobox.module.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
margin: 1rem 0;
55
}
66

7+
.full-width {
8+
display: block;
9+
margin: 0;
10+
}
11+
712
.size-sm {
813
padding: 0.5rem 0.75rem;
914
font-size: 0.875rem;

src/components/Ui/Infobox/Infobox.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ interface LabelProps {
88
size?: 'sm' | 'md' | 'lg';
99
variant?: 'normal' | 'success' | 'warning' | 'danger';
1010
children: ReactNode;
11+
fullWidth?: boolean;
1112
className?: string;
1213
}
1314

@@ -16,6 +17,7 @@ export const Infobox: React.FC<LabelProps> = ({
1617
size = 'md', // Default to medium size
1718
variant = 'normal', // Default to normal variant
1819
children,
20+
fullWidth = false,
1921
className,
2022
}) => {
2123
const infoboxClasses = cx(
@@ -28,6 +30,7 @@ export const Infobox: React.FC<LabelProps> = ({
2830
[styles['variant-success']]: variant === 'success',
2931
[styles['variant-warning']]: variant === 'warning',
3032
[styles['variant-danger']]: variant === 'danger',
33+
[styles['full-width']]: fullWidth,
3134
},
3235
className,
3336
);

0 commit comments

Comments
 (0)