diff --git a/apps/web/src/common/components/buttons/ActionMenuButton.vue b/apps/web/src/common/components/buttons/ActionMenuButton.vue index 5342cc512e..3934a82bad 100644 --- a/apps/web/src/common/components/buttons/ActionMenuButton.vue +++ b/apps/web/src/common/components/buttons/ActionMenuButton.vue @@ -5,8 +5,14 @@ import { ref, computed } from 'vue'; import { PIconButton, PContextMenu, useContextMenuStyle } from '@cloudforet/mirinae'; import type { MenuItem } from '@cloudforet/mirinae/types/controls/context-menu/type'; +const props = withDefaults(defineProps<{ + menu?: MenuItem[]; +}>(), { + menu: undefined, +}); const emit = defineEmits<{(event: 'edit'): void; (event: 'delete'): void; + (event: string): void; // for other menu names }>(); const containerRef = ref(null); @@ -21,7 +27,7 @@ useContextMenuStyle({ position: 'right', menuWidth: '192px', }); -const menu = computed(() => [ +const menu = computed(() => props.menu ?? [ { name: 'edit', icon: 'ic_edit', label: 'Edit' }, { name: 'delete', icon: 'ic_delete', label: 'Delete' }, ]); @@ -34,11 +40,7 @@ const hideMenu = () => { onClickOutside(containerRef, hideMenu); const handleSelectMenu = (item: MenuItem) => { - if (item.name === 'edit') { - emit('edit'); - } else if (item.name === 'delete') { - emit('delete'); - } + emit(item.name); hideMenu(); }; diff --git a/apps/web/src/services/ops-flow/components/AssociatedCategories.vue b/apps/web/src/services/ops-flow/components/AssociatedCategories.vue new file mode 100644 index 0000000000..df35d76713 --- /dev/null +++ b/apps/web/src/services/ops-flow/components/AssociatedCategories.vue @@ -0,0 +1,35 @@ + + + + diff --git a/apps/web/src/services/ops-flow/components/AssociatedWorkspaces.vue b/apps/web/src/services/ops-flow/components/AssociatedWorkspaces.vue new file mode 100644 index 0000000000..1a0ef700e4 --- /dev/null +++ b/apps/web/src/services/ops-flow/components/AssociatedWorkspaces.vue @@ -0,0 +1,35 @@ + + + + diff --git a/apps/web/src/services/ops-flow/components/PackageDeleteModal.vue b/apps/web/src/services/ops-flow/components/PackageDeleteModal.vue new file mode 100644 index 0000000000..34812232bc --- /dev/null +++ b/apps/web/src/services/ops-flow/components/PackageDeleteModal.vue @@ -0,0 +1,58 @@ + + + diff --git a/apps/web/src/services/ops-flow/components/PackageForm.vue b/apps/web/src/services/ops-flow/components/PackageForm.vue index 029bd6805d..2d7baf0123 100644 --- a/apps/web/src/services/ops-flow/components/PackageForm.vue +++ b/apps/web/src/services/ops-flow/components/PackageForm.vue @@ -70,7 +70,7 @@ const { name(value: string) { if (!value.trim().length) return 'Name is required'; if (value.length > 50) return 'Name should be less than 50 characters'; - if (packageStore.getters.packages.some((p) => p.package_id !== taskManagementPageState.editTargetPackageId && p.name === value)) return 'Name already exists'; + if (packageStore.getters.packages.some((p) => p.package_id !== taskManagementPageState.targetPackageId && p.name === value)) return 'Name already exists'; return true; }, description(value: string) { @@ -87,10 +87,10 @@ const handleCancelOrClose = () => { const handleConfirm = async () => { if (!isAllValid.value) return; loading.value = true; - if (taskManagementPageState.editTargetPackageId) { + if (taskManagementPageState.targetPackageId) { try { const updatedPackage = await packageStore.update({ - package_id: taskManagementPageState.editTargetPackageId, + package_id: taskManagementPageState.targetPackageId, name: name.value, description: description.value, tags: {}, @@ -123,7 +123,7 @@ const handleConfirm = async () => { loading.value = false; }; -watch([() => taskManagementPageState.visiblePackageForm, () => taskManagementPageGetters.editTargetPackage], async ([visible, targetPackage], [prevVisible]) => { +watch([() => taskManagementPageState.visiblePackageForm, () => taskManagementPageGetters.targetPackage], async ([visible, targetPackage], [prevVisible]) => { if (!visible) { if (!prevVisible) return; // prevent initial call await nextTick(); // wait for closing animation diff --git a/apps/web/src/services/ops-flow/components/SupportPackagePanel.vue b/apps/web/src/services/ops-flow/components/PackagePanel.vue similarity index 55% rename from apps/web/src/services/ops-flow/components/SupportPackagePanel.vue rename to apps/web/src/services/ops-flow/components/PackagePanel.vue index d5a571a1da..eb98f92971 100644 --- a/apps/web/src/services/ops-flow/components/SupportPackagePanel.vue +++ b/apps/web/src/services/ops-flow/components/PackagePanel.vue @@ -2,10 +2,13 @@ import { reactive, computed } from 'vue'; import { - PPaneLayout, PHeadingLayout, PHeading, PButton, PDataTable, PIconButton, + PPaneLayout, PHeadingLayout, PHeading, PButton, PDataTable, PIconButton, PBadge, } from '@cloudforet/mirinae'; +import type { MenuItem } from '@cloudforet/mirinae/types/controls/context-menu/type'; import type { DataTableField } from '@cloudforet/mirinae/types/data-display/tables/data-table/type'; +import ActionMenuButton from '@/common/components/buttons/ActionMenuButton.vue'; + import { useTaskManagementPageStore } from '@/services/ops-flow/stores/admin/task-management-page-store'; @@ -29,6 +32,11 @@ const state = reactive({ label: ' ', }, ]), + menu: computed(() => [ + { name: 'edit', icon: 'ic_edit', label: 'Edit' }, + { name: 'set-as-default', icon: 'ic_check-circle', label: 'Set as Default' }, + { name: 'delete', icon: 'ic_delete', label: 'Delete' }, + ]), }); @@ -41,7 +49,9 @@ const state = reactive({ /> diff --git a/apps/web/src/services/ops-flow/components/TaskFieldsConfiguration.vue b/apps/web/src/services/ops-flow/components/TaskFieldsConfiguration.vue new file mode 100644 index 0000000000..39a3ed37df --- /dev/null +++ b/apps/web/src/services/ops-flow/components/TaskFieldsConfiguration.vue @@ -0,0 +1,10 @@ + + + diff --git a/apps/web/src/services/ops-flow/components/TaskTypeForm.vue b/apps/web/src/services/ops-flow/components/TaskTypeForm.vue index 0192bd18dc..fbb8eb6d4a 100644 --- a/apps/web/src/services/ops-flow/components/TaskTypeForm.vue +++ b/apps/web/src/services/ops-flow/components/TaskTypeForm.vue @@ -1,27 +1,41 @@ diff --git a/apps/web/src/services/ops-flow/stores/admin/package-store.ts b/apps/web/src/services/ops-flow/stores/admin/package-store.ts index d288b03bb4..0ca2c1a6ab 100644 --- a/apps/web/src/services/ops-flow/stores/admin/package-store.ts +++ b/apps/web/src/services/ops-flow/stores/admin/package-store.ts @@ -116,6 +116,9 @@ export const usePackageStore = defineStore('package', () => { async setDefaultPackage(packageId: string) { return new Promise((resolve, reject) => { setTimeout(() => { + const prevDefaultPackage = getters.packages.find((p) => p.is_default); + if (prevDefaultPackage?.package_id === packageId) return; + if (prevDefaultPackage) prevDefaultPackage.is_default = false; const targetPackage = getters.packages.find((p) => p.package_id === packageId); if (targetPackage) { targetPackage.is_default = true; @@ -126,6 +129,19 @@ export const usePackageStore = defineStore('package', () => { }, 1000); }); }, + async delete(packageId: string) { + return new Promise((resolve, reject) => { + setTimeout(() => { + const index = state.items?.findIndex((p) => p.package_id === packageId); + if (index !== undefined && index >= 0) { + state.items?.splice(index, 1); + resolve(); + } else { + reject(new Error('Package not found')); + } + }, 1000); + }); + }, }; return { state, diff --git a/apps/web/src/services/ops-flow/stores/admin/task-category-store.ts b/apps/web/src/services/ops-flow/stores/admin/task-category-store.ts index c78eb8a174..c077209038 100644 --- a/apps/web/src/services/ops-flow/stores/admin/task-category-store.ts +++ b/apps/web/src/services/ops-flow/stores/admin/task-category-store.ts @@ -244,6 +244,19 @@ export const useTaskCategoryStore = defineStore('task-category', () => { }, 1000); }); }, + async delete(categoryId: string) { + return new Promise((resolve, reject) => { + setTimeout(() => { + const targetCategoryIndex = state.items?.findIndex((category) => category.category_id === categoryId); + if (targetCategoryIndex !== undefined && targetCategoryIndex >= 0) { + state.items?.splice(targetCategoryIndex, 1); + resolve(); + } else { + reject(new Error('Category not found')); + } + }, 1000); + }); + }, }; return { state, diff --git a/apps/web/src/services/ops-flow/stores/admin/task-management-page-store.ts b/apps/web/src/services/ops-flow/stores/admin/task-management-page-store.ts index ea66b4069b..351b8f9393 100644 --- a/apps/web/src/services/ops-flow/stores/admin/task-management-page-store.ts +++ b/apps/web/src/services/ops-flow/stores/admin/task-management-page-store.ts @@ -6,6 +6,9 @@ import { defineStore } from 'pinia'; import type { PackageModel } from '@/schema/identity/package/model'; import type { TaskCategoryModel } from '@/schema/opsflow/task-category/model'; +import type { WorkspaceItem } from '@/store/reference/workspace-reference-store'; +import { useWorkspaceReferenceStore } from '@/store/reference/workspace-reference-store'; + import { usePackageStore } from '@/services/ops-flow/stores/admin/package-store'; import { useTaskCategoryStore } from '@/services/ops-flow/stores/admin/task-category-store'; // import { useTaskTypeStore } from '@/services/ops-flow/stores/task-type-store'; @@ -14,34 +17,50 @@ interface UseTaskManagementPageStoreState { currentTemplateId?: string; // support package visiblePackageForm: boolean; - editTargetPackageId?: string; + targetPackageId?: string; + visibleDeletePackageModal: boolean; + visibleSetDefaultPackageModal: boolean; // category visibleCategoryForm: boolean; - editTargetCategoryId?: string; + targetCategoryId?: string; + visibleDeleteCategoryModal: boolean; } interface UseTaskManagementPageStoreGetters { - editTargetPackage: ComputedRef; - editTargetCategory: ComputedRef|undefined>; + targetPackage: ComputedRef; + associatedCategoriesToPackage: ComputedRef>; + associatedWorkspacesToPackage: ComputedRef; + targetCategory: ComputedRef|undefined>; defaultPackage: ComputedRef; } export const useTaskManagementPageStore = defineStore('task-management-page', () => { const packageStore = usePackageStore(); const taskCategoryStore = useTaskCategoryStore(); + const workspaceReferenceStore = useWorkspaceReferenceStore(); // const taskTypeStore = useTaskTypeStore(); const state = reactive({ currentTemplateId: 'support-center', // support package visiblePackageForm: false, - editTargetPackageId: undefined, + targetPackageId: undefined, + visibleDeletePackageModal: false, + visibleSetDefaultPackageModal: false, // category visibleCategoryForm: false, - editTargetCategoryId: undefined, + targetCategoryId: undefined, + visibleDeleteCategoryModal: false, }); const getters = reactive({ - editTargetPackage: computed(() => packageStore.getters.packages.find((p) => p.package_id === state.editTargetPackageId)), - editTargetCategory: computed|undefined>(() => taskCategoryStore.getters.taskCategories.find((c) => c.category_id === state.editTargetCategoryId)), + targetPackage: computed(() => packageStore.getters.packages.find((p) => p.package_id === state.targetPackageId)), + associatedCategoriesToPackage: computed>(() => taskCategoryStore.getters.taskCategories.filter((c) => c.package_id === state.targetPackageId)), + associatedWorkspacesToPackage: computed(() => { + const targetPackageId = state.targetPackageId; + if (!targetPackageId) return []; + const workspaceItems: WorkspaceItem[] = Object.values(workspaceReferenceStore.getters.workspaceItems); + return workspaceItems.filter((w) => w.data.packages?.includes(targetPackageId)); + }), + targetCategory: computed|undefined>(() => taskCategoryStore.getters.taskCategories.find((c) => c.category_id === state.targetCategoryId)), defaultPackage: computed(() => packageStore.getters.packages.find((p) => p.is_default)), }); const actions = { @@ -50,29 +69,53 @@ export const useTaskManagementPageStore = defineStore('task-management-page', () }, // support package openAddPackageForm() { - state.editTargetPackageId = undefined; + state.targetPackageId = undefined; state.visiblePackageForm = true; }, openEditPackageForm(packageId: string) { - state.editTargetPackageId = packageId; + state.targetPackageId = packageId; state.visiblePackageForm = true; }, closePackageForm() { state.visiblePackageForm = false; - state.editTargetPackageId = undefined; + state.targetPackageId = undefined; + }, + openDeletePackageModal(packageId: string) { + state.targetPackageId = packageId; + state.visibleDeletePackageModal = true; + }, + closeDeletePackageModal() { + state.visibleDeletePackageModal = false; + state.targetPackageId = undefined; + }, + openSetDefaultPackageModal(packageId: string) { + state.targetPackageId = packageId; + state.visibleSetDefaultPackageModal = true; + }, + closeSetDefaultPackageModal() { + state.visibleSetDefaultPackageModal = false; + state.targetPackageId = undefined; }, // category openAddCategoryForm() { - state.editTargetCategoryId = undefined; + state.targetCategoryId = undefined; state.visibleCategoryForm = true; }, openEditCategoryForm(categoryId: string) { - state.editTargetCategoryId = categoryId; + state.targetCategoryId = categoryId; state.visibleCategoryForm = true; }, closeCategoryForm() { state.visibleCategoryForm = false; - state.editTargetCategoryId = undefined; + state.targetCategoryId = undefined; + }, + openDeleteCategoryModal(categoryId: string) { + state.targetCategoryId = categoryId; + state.visibleDeleteCategoryModal = true; + }, + closeDeleteCategoryModal() { + state.visibleDeleteCategoryModal = false; + state.targetCategoryId = undefined; }, }; return { diff --git a/apps/web/src/services/ops-flow/task-fields-configuration/TaskFieldGenerator.vue b/apps/web/src/services/ops-flow/task-fields-configuration/TaskFieldGenerator.vue new file mode 100644 index 0000000000..0e2ef90574 --- /dev/null +++ b/apps/web/src/services/ops-flow/task-fields-configuration/TaskFieldGenerator.vue @@ -0,0 +1,24 @@ + + + diff --git a/apps/web/src/services/ops-flow/task-fields-configuration/TaskFieldGeneratorDetails.vue b/apps/web/src/services/ops-flow/task-fields-configuration/TaskFieldGeneratorDetails.vue new file mode 100644 index 0000000000..d335fbf243 --- /dev/null +++ b/apps/web/src/services/ops-flow/task-fields-configuration/TaskFieldGeneratorDetails.vue @@ -0,0 +1,36 @@ + + + diff --git a/apps/web/src/services/ops-flow/task-fields-configuration/TaskFieldGeneratorHeader.vue b/apps/web/src/services/ops-flow/task-fields-configuration/TaskFieldGeneratorHeader.vue new file mode 100644 index 0000000000..496750730e --- /dev/null +++ b/apps/web/src/services/ops-flow/task-fields-configuration/TaskFieldGeneratorHeader.vue @@ -0,0 +1,17 @@ + + + diff --git a/apps/web/src/services/ops-flow/task-fields-configuration/task-field-options-generator/options-generators/DropdownOptionsGenerator.vue b/apps/web/src/services/ops-flow/task-fields-configuration/task-field-options-generator/options-generators/DropdownOptionsGenerator.vue new file mode 100644 index 0000000000..86a2579b3a --- /dev/null +++ b/apps/web/src/services/ops-flow/task-fields-configuration/task-field-options-generator/options-generators/DropdownOptionsGenerator.vue @@ -0,0 +1,19 @@ + + + + diff --git a/apps/web/src/services/ops-flow/task-fields-configuration/task-field-options-generator/options-generators/ParagraphOptionsGenerator.vue b/apps/web/src/services/ops-flow/task-fields-configuration/task-field-options-generator/options-generators/ParagraphOptionsGenerator.vue new file mode 100644 index 0000000000..1ad22f98d0 --- /dev/null +++ b/apps/web/src/services/ops-flow/task-fields-configuration/task-field-options-generator/options-generators/ParagraphOptionsGenerator.vue @@ -0,0 +1,8 @@ + + + diff --git a/apps/web/src/store/reference/user-reference-store.ts b/apps/web/src/store/reference/user-reference-store.ts index fd5f37a353..39d5dfc88c 100644 --- a/apps/web/src/store/reference/user-reference-store.ts +++ b/apps/web/src/store/reference/user-reference-store.ts @@ -30,7 +30,7 @@ import ErrorHandler from '@/common/composables/error/errorHandler'; interface UserResourceItemData { roleInfo?: Partial; } -type UserReferenceItem = Required, 'key'|'label'|'name'|'data'>>; +export type UserReferenceItem = Required, 'key'|'label'|'name'|'data'>>; export type UserReferenceMap = ReferenceMap; const LOAD_TTL = 1000 * 60 * 60 * 3; // 3 hours