diff --git a/public/locales/en.json b/public/locales/en.json index 8c396583..f1a83b47 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -139,6 +139,7 @@ "createButton": "Create", "cancelButton": "Cancel", "chargingTargetLabel": "Charging Target", + "chargingTargetTypeLabel": "Charging Target Type", "displayNameLabel": "Display Name", "nameLabel": "Name", "metadataHeader": "Metadata", @@ -282,7 +283,9 @@ "name": "Name", "componentSelection": "Component Selection", "search": "Search", - "components": "Components" + "components": "Components", + "notSelected": "Not selected", + "btp": "BTP" }, "buttons": { "viewResource": "View resource", diff --git a/src/components/Dialogs/CreateProjectDialog.cy.tsx b/src/components/Dialogs/CreateProjectDialog.cy.tsx index 3cc7b79a..69188bbf 100644 --- a/src/components/Dialogs/CreateProjectDialog.cy.tsx +++ b/src/components/Dialogs/CreateProjectDialog.cy.tsx @@ -55,6 +55,7 @@ export const CreateProjectWorkspaceDialogWrapper: React.FC<{ }; return ( { resetField('name'); resetField('chargingTarget'); resetField('displayName'); + resetField('chargingTargetType'); }, [resetField]); useEffect(() => { @@ -79,6 +80,7 @@ export function CreateProjectDialogContainer({ name, chargingTarget, displayName, + chargingTargetType, members, }: OnCreatePayload): Promise => { try { @@ -87,6 +89,7 @@ export function CreateProjectDialogContainer({ displayName: displayName, chargingTarget: chargingTarget, members: members, + chargingTargetType: chargingTargetType, }), ); setIsOpen(false); @@ -115,6 +118,7 @@ export function CreateProjectDialogContainer({ register={register} errors={errors} setValue={setValue} + type={'project'} onCreate={handleSubmit(handleProjectCreate)} /> ); diff --git a/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx b/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx index 894d8a87..6bfe988a 100644 --- a/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx +++ b/src/components/Dialogs/CreateProjectWorkspaceDialog.tsx @@ -20,6 +20,7 @@ export type OnCreatePayload = { name: string; displayName?: string; chargingTarget?: string; + chargingTargetType?: string; members: Member[]; }; @@ -34,6 +35,7 @@ export interface CreateProjectWorkspaceDialogProps { errors: FieldErrors; setValue: UseFormSetValue; projectName?: string; + type: 'workspace' | 'project'; } export function CreateProjectWorkspaceDialog({ @@ -47,6 +49,7 @@ export function CreateProjectWorkspaceDialog({ errors, setValue, projectName, + type, }: CreateProjectWorkspaceDialogProps) { const { t } = useTranslation(); const [isKubectlDialogOpen, setIsKubectlDialogOpen] = useState(false); @@ -87,6 +90,8 @@ export function CreateProjectWorkspaceDialog({ { resetField('name'); resetField('chargingTarget'); resetField('displayName'); + resetField('chargingTargetType'); }, [resetField]); useEffect(() => { @@ -127,6 +129,7 @@ export function CreateWorkspaceDialogContainer({ register={register} errors={errors} setValue={setValue} + type={'workspace'} projectName={project} onCreate={handleSubmit(handleWorkspaceCreate)} /> diff --git a/src/components/Dialogs/MetadataForm.tsx b/src/components/Dialogs/MetadataForm.tsx index a69f088c..1267733a 100644 --- a/src/components/Dialogs/MetadataForm.tsx +++ b/src/components/Dialogs/MetadataForm.tsx @@ -1,24 +1,52 @@ -import { FieldErrors, UseFormRegister } from 'react-hook-form'; +import { FieldErrors, UseFormRegister, UseFormSetValue } from 'react-hook-form'; import { CreateDialogProps } from './CreateWorkspaceDialogContainer.tsx'; import { useTranslation } from 'react-i18next'; -import { Form, FormGroup, Input, Label } from '@ui5/webcomponents-react'; +import { + Form, + FormGroup, + Input, + Label, + Option, + Select, + SelectDomRef, + Ui5CustomEvent, +} from '@ui5/webcomponents-react'; import styles from './CreateProjectWorkspaceDialog.module.css'; +import React from 'react'; export interface MetadataFormProps { register: UseFormRegister; errors: FieldErrors; - + setValue: UseFormSetValue; sideFormContent?: React.ReactNode; + requireChargingTarget?: boolean; +} + +interface SelectOption { + label: string; + value: string; } export function MetadataForm({ register, errors, - + setValue, sideFormContent, + requireChargingTarget = false, }: MetadataFormProps) { const { t } = useTranslation(); - + const handleChargingTargetTypeChange = ( + event: Ui5CustomEvent, + ) => { + const selectedOption = event.detail.selectedOption as HTMLElement; + setValue('chargingTargetType', selectedOption.dataset.value); + }; + const chargingTypes: SelectOption[] = [ + ...(!requireChargingTarget + ? [{ label: t('common.notSelected'), value: '' }] + : []), + { label: t('common.btp'), value: 'btp' }, + ]; return (
{errors.name?.message}} required /> - @@ -45,8 +72,21 @@ export function MetadataForm({ {...register('displayName')} className={styles.input} /> - - + {sideFormContent ? sideFormContent : null}
); diff --git a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx index 2c8bd120..6f4e43ed 100644 --- a/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx +++ b/src/components/Wizards/CreateManagedControlPlaneWizardContainer.tsx @@ -45,6 +45,7 @@ export type CreateDialogProps = { name: string; displayName?: string; chargingTarget?: string; + chargingTargetType?: string; members: Member[]; }; @@ -96,6 +97,7 @@ export const CreateManagedControlPlaneWizardContainer: FC< name: '', displayName: '', chargingTarget: '', + chargingTargetType: '', members: [], }, mode: 'onChange', @@ -121,6 +123,7 @@ export const CreateManagedControlPlaneWizardContainer: FC< const clearFormFields = useCallback(() => { resetField('name'); resetField('chargingTarget'); + resetField('chargingTargetType'); resetField('displayName'); }, [resetField]); @@ -294,7 +297,11 @@ export const CreateManagedControlPlaneWizardContainer: FC< selected={selectedStep === 'metadata'} data-step="metadata" > - + { @@ -41,6 +43,7 @@ export const CreateProject = ( [DISPLAY_NAME_ANNOTATION]: optional?.displayName ?? '', }, labels: { + [CHARGING_TARGET_TYPE_LABEL]: optional?.chargingTargetType ?? '', [CHARGING_TARGET_LABEL]: optional?.chargingTarget ?? '', }, }, diff --git a/src/lib/api/types/crate/createWorkspace.ts b/src/lib/api/types/crate/createWorkspace.ts index 4524e979..49783f09 100644 --- a/src/lib/api/types/crate/createWorkspace.ts +++ b/src/lib/api/types/crate/createWorkspace.ts @@ -1,6 +1,7 @@ import { Resource } from '../resource'; import { CHARGING_TARGET_LABEL, + CHARGING_TARGET_TYPE_LABEL, DISPLAY_NAME_ANNOTATION, } from '../shared/keyNames'; import { Member } from '../shared/members'; @@ -31,6 +32,7 @@ export const CreateWorkspace = ( optional?: { displayName?: string; chargingTarget?: string; + chargingTargetType?: string; members?: Member[]; }, ): CreateWorkspaceType => { @@ -44,6 +46,7 @@ export const CreateWorkspace = ( [DISPLAY_NAME_ANNOTATION]: optional?.displayName ?? '', }, labels: { + [CHARGING_TARGET_TYPE_LABEL]: optional?.chargingTargetType ?? '', [CHARGING_TARGET_LABEL]: optional?.chargingTarget ?? '', }, }, diff --git a/src/lib/api/types/shared/keyNames.ts b/src/lib/api/types/shared/keyNames.ts index 48eb535e..f4671fb3 100644 --- a/src/lib/api/types/shared/keyNames.ts +++ b/src/lib/api/types/shared/keyNames.ts @@ -1,3 +1,5 @@ export const DISPLAY_NAME_ANNOTATION: string = 'openmcp.cloud/display-name'; export const CHARGING_TARGET_LABEL: string = 'openmcp.cloud.sap/charging-target'; +export const CHARGING_TARGET_TYPE_LABEL: string = + 'openmcp.cloud.sap/charging-target-type'; diff --git a/src/lib/api/validations/regex.ts b/src/lib/api/validations/regex.ts index 4310068c..3b46c506 100644 --- a/src/lib/api/validations/regex.ts +++ b/src/lib/api/validations/regex.ts @@ -5,3 +5,6 @@ export const projectWorkspaceNameRegex = // Matches managed control plane names: 1-63 chars per segment, lowercase alphanum/dash, dot-separated, no leading/trailing dash. export const managedControlPlaneNameRegex = /^(?!-)[a-z0-9-]{1,63}(? members?.length > 0), }); export const validationSchemaCreateManagedControlPlane = z.object({ @@ -31,5 +32,6 @@ export const validationSchemaCreateManagedControlPlane = z.object({ .max(25, t('validationErrors.max25chars')), displayName: z.string().optional(), chargingTarget: z.string().optional(), + chargingTargetType: z.string().optional(), members: z.array(member), });