Skip to content

Commit

Permalink
feat: add task management deletion scenarios (#5086)
Browse files Browse the repository at this point in the history
* feat: update action menu and refactor category/package management logic

Signed-off-by: Wanjin Noh <[email protected]>

* feat: add set default package modal and update related components

Signed-off-by: Wanjin Noh <[email protected]>

* feat: add delete package modal and associated categories/workspaces components

Signed-off-by: Wanjin Noh <[email protected]>

* refactor: rename package components for consistency and clarity

Signed-off-by: Wanjin Noh <[email protected]>

* feat: add task category delete modal and rename category form component

Signed-off-by: Wanjin Noh <[email protected]>

* fix: ensure modal closes after package deletion attempt

Signed-off-by: Wanjin Noh <[email protected]>

* feat: add refresh functionality to PackagePanel and TaskCategoryPanel

Signed-off-by: Wanjin Noh <[email protected]>

* feat(assignee-pool): implement user selection and initialization logic

Signed-off-by: Wanjin Noh <[email protected]>

* feat: add task fields configuration component and related generators

Signed-off-by: Wanjin Noh <[email protected]>

---------

Signed-off-by: Wanjin Noh <[email protected]>
  • Loading branch information
WANZARGEN authored Nov 25, 2024
1 parent 502f6e6 commit 0816f42
Show file tree
Hide file tree
Showing 23 changed files with 580 additions and 77 deletions.
14 changes: 8 additions & 6 deletions apps/web/src/common/components/buttons/ActionMenuButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLElement|null>(null);
Expand All @@ -21,7 +27,7 @@ useContextMenuStyle({
position: 'right',
menuWidth: '192px',
});
const menu = computed<MenuItem[]>(() => [
const menu = computed<MenuItem[]>(() => props.menu ?? [
{ name: 'edit', icon: 'ic_edit', label: 'Edit' },
{ name: 'delete', icon: 'ic_delete', label: 'Delete' },
]);
Expand All @@ -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();
};
</script>
Expand Down
35 changes: 35 additions & 0 deletions apps/web/src/services/ops-flow/components/AssociatedCategories.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script setup lang="ts">
import { computed } from 'vue';
import { PFieldTitle, PDataTable } from '@cloudforet/mirinae';
import type { DataTableField } from '@cloudforet/mirinae/types/data-display/tables/data-table/type';
import { useTaskManagementPageStore } from '@/services/ops-flow/stores/admin/task-management-page-store';
const taskManagementPageStore = useTaskManagementPageStore();
const fields = computed<DataTableField[]>(() => [
{
name: 'name',
label: 'Name',
width: '30%',
},
{
name: 'description',
label: 'Description',
width: '70%',
},
]);
</script>

<template>
<div>
<p-field-title class="mb-2">
Category
</p-field-title>
<p-data-table :fields="fields"
:items="taskManagementPageStore.getters.associatedCategoriesToPackage"
/>
</div>
</template>

35 changes: 35 additions & 0 deletions apps/web/src/services/ops-flow/components/AssociatedWorkspaces.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script setup lang="ts">
import { computed } from 'vue';
import { PFieldTitle, PDataTable } from '@cloudforet/mirinae';
import type { DataTableField } from '@cloudforet/mirinae/types/data-display/tables/data-table/type';
import { useTaskManagementPageStore } from '@/services/ops-flow/stores/admin/task-management-page-store';
const taskManagementPageStore = useTaskManagementPageStore();
const fields = computed<DataTableField[]>(() => [
{
name: 'name',
label: 'Name',
width: '30%',
},
{
name: 'description',
label: 'Description',
width: '70%',
},
]);
</script>

<template>
<div>
<p-field-title class="mb-2">
Associated Workspaces
</p-field-title>
<p-data-table :fields="fields"
:items="taskManagementPageStore.getters.associatedWorkspacesToPackage"
/>
</div>
</template>

58 changes: 58 additions & 0 deletions apps/web/src/services/ops-flow/components/PackageDeleteModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<script setup lang="ts">
import { ref, computed } from 'vue';
import { PButtonModal } from '@cloudforet/mirinae';
import ErrorHandler from '@/common/composables/error/errorHandler';
import AssociatedCategories from '@/services/ops-flow/components/AssociatedCategories.vue';
import AssociatedWorkspaces from '@/services/ops-flow/components/AssociatedWorkspaces.vue';
import { useTaskManagementPageStore } from '@/services/ops-flow/stores/admin/task-management-page-store';
const taskManagementPageStore = useTaskManagementPageStore();
const packageStore = taskManagementPageStore.packageStore;
const deletable = computed(() => !taskManagementPageStore.getters.associatedCategoriesToPackage.length
&& !taskManagementPageStore.getters.associatedWorkspacesToPackage.length);
const loading = ref<boolean>(false);
const handleConfirm = async () => {
loading.value = true;
try {
if (!taskManagementPageStore.state.targetPackageId) {
throw new Error('[Console Error] Cannot delete package without a target package');
}
await packageStore.delete(taskManagementPageStore.state.targetPackageId);
} catch (e) {
ErrorHandler.handleRequestError(e, 'Failed to delete package');
} finally {
taskManagementPageStore.closeDeletePackageModal();
loading.value = false;
}
};
const handleCloseOrCancel = () => {
taskManagementPageStore.closeDeletePackageModal();
};
</script>

<template>
<p-button-modal :visible="taskManagementPageStore.state.visibleDeletePackageModal"
theme-color="alert"
:header-title="deletable ? 'Are you sure you want to delete this package?' : 'Delete Package'"
:size="deletable ? 'sm' : 'md'"
:loading="loading"
:disabled="!deletable"
@confirm="handleConfirm"
@close="handleCloseOrCancel"
@cancel="handleCloseOrCancel"
>
<template #body>
<p v-if="!deletable"
class="text-paragraph-lg font-bold mb-4"
>
To reassign them to a different package, update the associations before deleting.
</p>
<associated-categories v-if="!!taskManagementPageStore.getters.associatedCategoriesToPackage.length" />
<associated-workspaces v-if="!!taskManagementPageStore.getters.associatedWorkspacesToPackage.length" />
</template>
</p-button-modal>
</template>
8 changes: 4 additions & 4 deletions apps/web/src/services/ops-flow/components/PackageForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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: {},
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -29,6 +32,11 @@ const state = reactive({
label: ' ',
},
]),
menu: computed<MenuItem[]>(() => [
{ 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' },
]),
});
</script>

Expand All @@ -41,7 +49,9 @@ const state = reactive({
/>
</template>
<template #extra>
<p-icon-button name="ic_refresh" />
<p-icon-button name="ic_refresh"
@click="packageStore.list()"
/>
<p-button icon-left="ic_plus_bold"
size="md"
style-type="substitutive"
Expand All @@ -58,21 +68,27 @@ const state = reactive({
:items="packageStore.getters.packages"
:fields="state.packageFields"
>
<template #col-name-format="{ item }">
<span class="inline-flex items-center gap-2">
{{ item.name }}
<p-badge v-if="item.is_default"
badge-type="solid-outline"
style-type="gray500"
>
Default
</p-badge>
</span>
</template>
<template #col-buttons-format="{ item }">
<p-button icon-left="ic_edit"
size="sm"
style-type="tertiary"
@click="taskManagementPageStore.openEditPackageForm(item.package_id)"
>
Edit
</p-button>
<p-button class="ml-2"
icon-left="ic_delete"
size="sm"
style-type="tertiary"
>
Delete
</p-button>
<div class="flex justify-end">
<action-menu-button :menu="item.package_id === taskManagementPageStore.getters.defaultPackage?.package_id
? undefined
: state.menu"
@edit="taskManagementPageStore.openEditPackageForm(item.package_id)"
@set-as-default="taskManagementPageStore.openSetDefaultPackageModal(item.package_id)"
@delete="taskManagementPageStore.openDeletePackageModal(item.package_id)"
/>
</div>
</template>
</p-data-table>
</p-pane-layout>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<script setup lang="ts">
import { ref, computed } from 'vue';
import { PButtonModal } from '@cloudforet/mirinae';
import ErrorHandler from '@/common/composables/error/errorHandler';
import { useTaskManagementPageStore } from '@/services/ops-flow/stores/admin/task-management-page-store';
const taskManagementPageStore = useTaskManagementPageStore();
const packageStore = taskManagementPageStore.packageStore;
const loading = ref<boolean>(false);
const name = computed(() => taskManagementPageStore.getters.targetPackage?.name ?? '');
const handleConfirm = async () => {
loading.value = true;
try {
if (!taskManagementPageStore.state.targetPackageId) {
throw new Error('[Console Error] Cannot set default package without a target package');
}
await packageStore.setDefaultPackage(taskManagementPageStore.state.targetPackageId);
taskManagementPageStore.closeSetDefaultPackageModal();
} catch (e) {
ErrorHandler.handleRequestError(e, 'Failed to set default package');
} finally {
loading.value = false;
}
};
const handleCloseOrCancel = () => {
taskManagementPageStore.closeSetDefaultPackageModal();
};
</script>

<template>
<p-button-modal :visible="taskManagementPageStore.state.visibleSetDefaultPackageModal"
size="sm"
:loading="loading"
@confirm="handleConfirm"
@close="handleCloseOrCancel"
@cancel="handleCloseOrCancel"
>
<template #header-title>
Set <strong>{{ name }}</strong> as the default package.
</template>
<template #body>
This will make it the primary package for related operations.
</template>
</p-button-modal>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<script setup lang="ts">
import { ref } from 'vue';
import DeleteModal from '@/common/components/modals/DeleteModal.vue';
import ErrorHandler from '@/common/composables/error/errorHandler';
import { useTaskManagementPageStore } from '@/services/ops-flow/stores/admin/task-management-page-store';
const taskManagementPageStore = useTaskManagementPageStore();
const taskCategoryStore = taskManagementPageStore.taskCategoryStore;
const loading = ref<boolean>(false);
const handleConfirm = async () => {
try {
loading.value = true;
if (!taskManagementPageStore.state.targetCategoryId) {
throw new Error('[Console Error] Cannot delete category without a target category');
}
await taskCategoryStore.delete(taskManagementPageStore.state.targetCategoryId);
} catch (e) {
ErrorHandler.handleRequestError(e, 'Failed to delete category');
} finally {
taskManagementPageStore.closeDeleteCategoryModal();
loading.value = false;
}
};
const handleCloseOrCancel = () => {
taskManagementPageStore.closeDeleteCategoryModal();
};
</script>

<template>
<delete-modal :visible="taskManagementPageStore.state.visibleDeleteCategoryModal"
header-title="Are you sure you want to delete this category?"
size="sm"
:loading="loading"
@confirm="handleConfirm"
@close="handleCloseOrCancel"
@cancel="handleCloseOrCancel"
/>
</template>
Loading

0 comments on commit 0816f42

Please sign in to comment.