Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: markup for the first step of the service creation page #5123

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
<script setup lang="ts">
import {
computed, onMounted, reactive, watch,
} from 'vue';
import { useRouter } from 'vue-router/composables';

import { SpaceConnector } from '@cloudforet/core-lib/space-connector';
import {
PFieldGroup, PTextInput, PTooltip, PI, PSelectDropdown, PAvatar, PButton,
} from '@cloudforet/mirinae';
import type { SelectDropdownMenuItem } from '@cloudforet/mirinae/types/controls/dropdown/select-dropdown/type';

import type { ListResponse } from '@/schema/_common/api-verbs/list';
import type { WorkspaceUserListParameters } from '@/schema/identity/workspace-user/api-verbs/list';
import type { WorkspaceUserModel } from '@/schema/identity/workspace-user/model';
import { i18n } from '@/translations';

import ErrorHandler from '@/common/composables/error/errorHandler';
import { useFormValidator } from '@/common/composables/form-validator';

import { ALERT_MANAGER_V2_ROUTE } from '@/services/alert-manager-v2/routes/route-constant';
import { useServiceFormStore } from '@/services/alert-manager-v2/store/service-form-store';

const emit = defineEmits<{(e: 'update:currentStep', step: number): void}>();

const serviceFormStore = useServiceFormStore();
const serviceFormState = serviceFormStore.state;

const router = useRouter();

const formDataState = reactive({
workspaceUserLoading: false,
workspaceUsersData: [] as WorkspaceUserModel[],
workspaceUsers: computed<SelectDropdownMenuItem[]>(() => [
{ type: 'header', label: i18n.t('ALERT_MANAGER.SERVICE.USER') },
...formDataState.workspaceUsersData.map((i) => ({
label: i.user_id,
name: i.user_id,
})),
]),
// TODO: Add user group data
selectedMemberItems: [] as SelectDropdownMenuItem[],
});
const state = reactive({
isFocusedKey: false,
isAllFormValid: computed<boolean>(() => (isAllValid && (name && name.value !== '') && (key && key.value !== ''))),
});

const {
forms: {
name,
key,
description,
},
setForm,
invalidState,
invalidTexts,
isAllValid,
} = useFormValidator({
name: serviceFormState.name,
key: serviceFormState.key,
description: serviceFormState.description,
}, {
name() {
// TODO: Implement validation logic
return '';
},
key() {
// TODO: Implement validation logic
return '';
},
});

const convertToSnakeCase = (str) => str
.replace(/([a-zA-Z0-9가-힣]+)(?=[^a-zA-Z0-9가-힣]|$)/g, '$1_')
.replace(/\s+/g, '_')
.replace(/_+/g, '_')
.replace(/_$/g, '')
.toLowerCase();
const getUserName = (id: string) => formDataState.workspaceUsersData.find((i) => i.user_id === id)?.name;
const handleClickPrevButton = () => {
router.push({ name: ALERT_MANAGER_V2_ROUTE.SERVICE._NAME });
};
const handleClickNextButton = () => {
emit('update:currentStep', 2);
};
const handleChangeInput = (label: 'name'|'key'|'member'|'description', value?: string) => {
if (label !== 'member') setForm(label, value);
// TODO: Add logic to separate user group data
serviceFormStore.setFormStep1({
name: name.value,
key: key.value,
member: {
USER: formDataState.selectedMemberItems.map((i) => i.name),
},
description: description.value,
});
};

const listWorkspaceUsers = async () => {
formDataState.workspaceUserLoading = true;
try {
const { results } = await SpaceConnector.clientV2.identity.workspaceUser.list<WorkspaceUserListParameters, ListResponse<WorkspaceUserModel>>();
formDataState.workspaceUsersData = results || [];
} catch (e) {
ErrorHandler.handleError(e);
formDataState.workspaceUsersData = [];
} finally {
formDataState.workspaceUserLoading = false;
}
};

watch(() => state.isFocusedKey, (isFocusedKey) => {
if (isFocusedKey && !serviceFormState.key) {
handleChangeInput('key', convertToSnakeCase(serviceFormState.name));
}
});

onMounted(async () => {
await listWorkspaceUsers();
});
</script>

<template>
<div class="service-create-step1">
<form>
<p-field-group :label="$t('ALERT_MANAGER.SERVICE.LABEL_NAME')"
:invalid-text="invalidTexts.name"
:invalid="invalidState.name"
required
>
<template #default="{invalid}">
<p-text-input :value="name"
block
:invalid="invalid"
@update:value="handleChangeInput('name', $event)"
/>
</template>
</p-field-group>
<p-field-group :label="$t('ALERT_MANAGER.SERVICE.LABEL_KEY')"
:invalid-text="invalidTexts.key"
:invalid="invalidState.key"
required
>
<template #label-extra>
<p-tooltip :contents="$t('ALERT_MANAGER.SERVICE.LABEL_KEY_DESC')"
position="bottom"
>
<p-i width="1rem"
height="1rem"
name="ic_info-circle"
/>
</p-tooltip>
</template>
<template #default="{invalid}">
<p-text-input :value="key"
block
:is-focused.sync="state.isFocusedKey"
:invalid="invalid"
@update:value="handleChangeInput('key', $event)"
/>
</template>
</p-field-group>
<p-field-group :label="$t('ALERT_MANAGER.SERVICE.MEMBER')">
<!-- TODO: Add user group data-->
<p-select-dropdown :loading="formDataState.workspaceUserLoading"
:menu="formDataState.workspaceUsers"
:selected.sync="formDataState.selectedMemberItems"
multi-selectable
appearance-type="stack"
use-fixed-menu-style
is-filterable
show-delete-all-button
show-select-marker
@update:selected="handleChangeInput('member', $event)"
>
<template #menu-item--format="{ item }">
<div class="member-menu-item">
<p-avatar class="menu-icon"
size="sm"
/>
<span>{{ item.label }}</span>
<span v-if="getUserName(item.name)">({{ getUserName(item.name) }})</span>
</div>
</template>
</p-select-dropdown>
</p-field-group>
<p-field-group :label="$t('ALERT_MANAGER.DESCRIPTION')">
<p-text-input :value="description"
block
@update:value="handleChangeInput('description', $event)"
/>
</p-field-group>
</form>
<div class="buttons-wrapper">
<p-button style-type="transparent"
class="step-left-base-button"
size="lg"
@click="handleClickPrevButton"
>
{{ $t('ALERT_MANAGER.CANCEL') }}
</p-button>
<p-button :disabled="!state.isAllFormValid"
class="step-right-button"
style-type="substitutive"
icon-right="ic_arrow-right"
size="lg"
@click="handleClickNextButton"
>
{{ $t('ALERT_MANAGER.CONTINUE') }}
</p-button>
</div>
</div>
</template>

<style scoped lang="postcss">
.service-create-step1 {
.member-menu-item {
@apply flex items-center;
gap: 0.25rem;
}
.buttons-wrapper {
@apply flex justify-end;
padding-top: 2rem;
gap: 1rem;
}
}
</style>
64 changes: 64 additions & 0 deletions apps/web/src/services/alert-manager-v2/pages/ServiceCreatePage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<script setup lang="ts">
import { computed, reactive } from 'vue';
import type { TranslateResult } from 'vue-i18n';
import { useRouter } from 'vue-router/composables';

import { PCenteredLayoutHeader } from '@cloudforet/mirinae';

import { i18n } from '@/translations';

import ServiceCreateStep1 from '@/services/alert-manager-v2/components/ServiceCreateStep1.vue';
import { ALERT_MANAGER_V2_ROUTE } from '@/services/alert-manager-v2/routes/route-constant';

type headerInfoByStep = {
title: TranslateResult;
desc: TranslateResult;
};

const router = useRouter();

const state = reactive({
step: 1,
headerInfo: computed<headerInfoByStep>(() => {
if (state.step === 1) {
return {
title: i18n.t('ALERT_MANAGER.SERVICE.CREATE_SERVICE_TITLE'),
desc: i18n.t('ALERT_MANAGER.SERVICE.CREATE_SERVICE_DESC'),
};
}
if (state.step === 2) {
return {
title: i18n.t('ALERT_MANAGER.SERVICE.INTEGRATE_TOOL_TITLE'),
desc: i18n.t('ALERT_MANAGER.SERVICE.INTEGRATE_TOOL_DESC'),
};
}
return {
title: i18n.t('ALERT_MANAGER.NOTIFICATIONS.SET_UP_NOTIFICATIONS'),
desc: i18n.t('ALERT_MANAGER.NOTIFICATIONS.SET_UP_DESC'),
};
}),
});

const handleClickClose = () => {
router.push({ name: ALERT_MANAGER_V2_ROUTE.SERVICE._NAME });
};
const handleChangeStep = (step: number) => {
state.step = step;
};
</script>

<template>
<div class="service-create-page">
<p-centered-layout-header :title="state.headerInfo.title"
:description="state.headerInfo.desc"
show-step
:current-step="state.step"
:total-steps="3"
:show-close-button="true"
@close="handleClickClose"
/>
<service-create-step1 v-if="state.step === 1"
@update:currentStep="handleChangeStep"
/>
</div>
</template>
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
<script setup lang="ts">
import { useRouter } from 'vue-router/composables';

import {
PButton, PHeading, PHeadingLayout,
} from '@cloudforet/mirinae';

import ServiceList from '@/services/alert-manager-v2/components/ServiceList.vue';
import { ALERT_MANAGER_V2_ROUTE } from '@/services/alert-manager-v2/routes/route-constant';

const router = useRouter();

const handleClickCreateButton = () => {
console.log('TODO: handleClickCreateButton');
router.push({ name: ALERT_MANAGER_V2_ROUTE.SERVICE.CREATE._NAME });
};
</script>

Expand Down
7 changes: 7 additions & 0 deletions apps/web/src/services/alert-manager-v2/routes/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ALERT_MANAGER_V2_ROUTE } from '@/services/alert-manager-v2/routes/route
const AlertManagerContainer = () => import('@/services/alert-manager-v2/AlertManagerContainer.vue');
const ServiceMainPage = () => import('@/services/alert-manager-v2/pages/ServiceMainPage.vue');
const ServiceDetailPage = () => import('@/services/alert-manager-v2/pages/ServiceDetailPage.vue');
const ServiceCreatePage = () => import('@/services/alert-manager-v2/pages/ServiceCreatePage.vue');

const alertManagerV2Routes: RouteConfig = {
path: 'alert-manager-v2',
Expand All @@ -36,6 +37,12 @@ const alertManagerV2Routes: RouteConfig = {
meta: { menuId: MENU_ID.SERVICE },
component: ServiceMainPage as any,
},
{
path: 'create',
name: ALERT_MANAGER_V2_ROUTE.SERVICE.CREATE._NAME,
meta: { centeredLayout: true },
component: ServiceCreatePage as any,
},
{
path: ':serviceId',
name: ALERT_MANAGER_V2_ROUTE.SERVICE.DETAIL._NAME,
Expand Down
38 changes: 38 additions & 0 deletions apps/web/src/services/alert-manager-v2/store/service-form-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { reactive } from 'vue';

import { defineStore } from 'pinia';

type ServiceMemberType = {
USER?: string[],
USER_GROUP?: string[],
};

interface ServiceFormState {
name: string;
key: string;
member: ServiceMemberType;
description: string;
}

export const useServiceFormStore = defineStore('service-form', () => {
const state = reactive<ServiceFormState>({
name: '',
key: '',
member: {},
description: '',
});

const actions = {
setFormStep1(form: ServiceFormState) {
state.name = form.name;
state.key = form.key;
state.member = form.member;
state.description = form.description;
},
};

return {
state,
...actions,
};
});
Loading
Loading