Skip to content

Commit

Permalink
feat: add markups and data binding of task category page (#5073)
Browse files Browse the repository at this point in the history
* feat(ops-flow): implement task category detail navigation and refactor stores

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

* feat(admin): add path handling and update task management store name

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

* feat: add task status management components and refactor category store methods

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

* feat(task-status): enhance draggable item with color and improved layout

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

* feat(task-status): add action button for editing and deleting tasks

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

* feat(task-status): add status form and update task status handling

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

* feat: refactor package store to use getters and add delete modal component

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

* refactor(buttons): rename TaskStatusActionButton to ActionMenuButton

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

* feat(task-type): add assignee pool to task type creation and update forms

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

---------

Signed-off-by: Wanjin Noh <[email protected]>
  • Loading branch information
WANZARGEN authored Nov 21, 2024
1 parent f6f2ac3 commit 8099032
Show file tree
Hide file tree
Showing 28 changed files with 1,330 additions and 173 deletions.
69 changes: 69 additions & 0 deletions apps/web/src/common/components/buttons/ActionMenuButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<script setup lang="ts">
import { onClickOutside } from '@vueuse/core';
import { ref, computed } from 'vue';
import { PIconButton, PContextMenu, useContextMenuStyle } from '@cloudforet/mirinae';
import type { MenuItem } from '@cloudforet/mirinae/types/controls/context-menu/type';
const emit = defineEmits<{(event: 'edit'): void;
(event: 'delete'): void;
}>();
const containerRef = ref<HTMLElement|null>(null);
const targetRef = ref<HTMLElement|null>(null);
const menuRef = ref<any|null>(null);
const visibleMenu = ref<boolean>(false);
useContextMenuStyle({
targetRef,
menuRef,
visibleMenu,
useFixedMenuStyle: true,
position: 'right',
menuWidth: '192px',
});
const menu = computed<MenuItem[]>(() => [
{ name: 'edit', icon: 'ic_edit', label: 'Edit' },
{ name: 'delete', icon: 'ic_delete', label: 'Delete' },
]);
const toggleMenu = () => {
visibleMenu.value = !visibleMenu.value;
};
const hideMenu = () => {
visibleMenu.value = false;
};
onClickOutside(containerRef, hideMenu);
const handleSelectMenu = (item: MenuItem) => {
if (item.name === 'edit') {
emit('edit');
} else if (item.name === 'delete') {
emit('delete');
}
hideMenu();
};
</script>
<template>
<span ref="containerRef"
class="relative"
>
<p-icon-button ref="targetRef"
name="ic_ellipsis-horizontal"
@click="toggleMenu"
/>
<p-context-menu v-show="visibleMenu"
ref="menuRef"
:menu="menu"
:selected="[]"
class="action-menu"
@select="handleSelectMenu"
/>
</span>
</template>
<style scoped lang="postcss">
.action-menu {
@apply bg-white;
z-index: 1;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface TaskCategoryUpdateParameters {
category_id: string;
name?: string;
description?: string;
status_options: TaskStatusOptions;
status_options?: TaskStatusOptions;
fields?: TaskField[];
force?: boolean;
tags?: Tags;
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/schema/opsflow/task-type/api-verbs/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface TaskTypeCreateParameters {
name: string;
description?: string;
fields?: TaskField[];
assignee_pool?: string[];
tags?: Tags;
category_id: string;
}
1 change: 1 addition & 0 deletions apps/web/src/schema/opsflow/task-type/api-verbs/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface TaskTypeUpdateParameters {
name?: string;
description?: string;
fields?: TaskField[];
assignee_pool?: string[];
tags?: Tags;
category_id?: string;
}
2 changes: 1 addition & 1 deletion apps/web/src/schema/opsflow/task-type/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface TaskTypeModel {
name: string;
description: string;
fields: TaskField[];
assignee?: string;
assignee_pool?: string[];
tags: Tags;
category_id: string;
domain_id: string;
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/services/ops-flow/components/CategoryForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,14 @@ const handleConfirm = async () => {
try {
loading.value = true;
if (taskManagementPageState.editTargetCategoryId) {
await taskCategoryStore.updateCategory({
await taskCategoryStore.update({
category_id: taskManagementPageState.editTargetCategoryId,
name: name.value,
description: description.value,
});
} else {
if (!taskManagementPageGetters.defaultPackage) throw Error('Default package is not found');
await taskCategoryStore.createCategory({
await taskCategoryStore.create({
name: name.value,
description: description.value,
package_id: taskManagementPageGetters.defaultPackage.package_id,
Expand Down
14 changes: 5 additions & 9 deletions apps/web/src/services/ops-flow/components/PackageForm.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import {
onBeforeMount, toRef, ref, computed, watch, nextTick,
toRef, ref, computed, watch, nextTick,
} from 'vue';
import {
Expand Down Expand Up @@ -48,7 +48,7 @@ const {
setInitialCategories,
applyPackageToCategories,
} = useCategoryField({
defaultPackage: computed<PackageModel|undefined>(() => packageStore.state.packages?.find((p) => p.is_default)),
defaultPackage: computed<PackageModel|undefined>(() => packageStore.getters.packages.find((p) => p.is_default)),
taskCategoryStore,
});
Expand All @@ -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.state.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.editTargetPackageId && p.name === value)) return 'Name already exists';
return true;
},
description(value: string) {
Expand All @@ -89,7 +89,7 @@ const handleConfirm = async () => {
loading.value = true;
if (taskManagementPageState.editTargetPackageId) {
try {
const updatedPackage = await packageStore.updatePackage({
const updatedPackage = await packageStore.update({
package_id: taskManagementPageState.editTargetPackageId,
name: name.value,
description: description.value,
Expand All @@ -105,7 +105,7 @@ const handleConfirm = async () => {
}
} else {
try {
const createdPackage = await packageStore.createPackage({
const createdPackage = await packageStore.create({
name: name.value,
description: description.value,
tags: {},
Expand All @@ -123,10 +123,6 @@ const handleConfirm = async () => {
loading.value = false;
};
onBeforeMount(() => {
workspaceReferenceStore.load();
});
watch([() => taskManagementPageState.visiblePackageForm, () => taskManagementPageGetters.editTargetPackage], async ([visible, targetPackage], [prevVisible]) => {
if (!visible) {
if (!prevVisible) return; // prevent initial call
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script setup lang="ts">
import { onBeforeMount, reactive, computed } from 'vue';
import { reactive, computed } from 'vue';
import {
PPaneLayout, PHeadingLayout, PHeading, PButton, PDataTable, PIconButton,
} from '@cloudforet/mirinae';
import type { DataTableField } from '@cloudforet/mirinae/src/data-display/tables/data-table/type';
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';
Expand All @@ -30,10 +30,6 @@ const state = reactive({
},
]),
});
onBeforeMount(() => {
packageStore.fetchPackages();
});
</script>

<template>
Expand All @@ -59,7 +55,7 @@ onBeforeMount(() => {
계약에 따라 고객이 이용할 수 있는 기능의 묶음입니다. 전체 서비스의 범위를 나타내며, 하위 카테고를 생성할 수 있습니다.
</p>
<p-data-table :loading="packageStore.state.loading"
:items="packageStore.state.packages"
:items="packageStore.getters.packages"
:fields="state.packageFields"
>
<template #col-buttons-format="{ item }">
Expand Down
23 changes: 16 additions & 7 deletions apps/web/src/services/ops-flow/components/TaskCategoryPanel.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
<script setup lang="ts">
import { onBeforeMount, reactive, computed } from 'vue';
import { reactive, computed } from 'vue';
import {
PPaneLayout, PHeadingLayout, PHeading, PButton, PDataTable, PBadge, PIconButton,
PPaneLayout, PHeadingLayout, PHeading, PButton, PDataTable, PBadge, PIconButton, PLink,
} from '@cloudforet/mirinae';
import type { DataTableField } from '@cloudforet/mirinae/src/data-display/tables/data-table/type';
import { makeAdminRouteName } from '@/router/helpers/route-helper';
import { OPS_FLOW_ROUTE } from '@/services/ops-flow/routes/route-constant';
import { useTaskManagementPageStore } from '@/services/ops-flow/stores/admin/task-management-page-store';
Expand Down Expand Up @@ -37,17 +40,14 @@ const state = reactive({
},
]),
packageMap: computed(() => {
if (!packageStore.state.packages) return {};
return packageStore.state.packages.reduce((acc, cur) => {
if (!packageStore.getters.packages) return {};
return packageStore.getters.packages.reduce((acc, cur) => {
acc[cur.package_id] = cur;
return acc;
}, {} as Record<string, any>);
}),
});
onBeforeMount(() => {
// taskCategoryStore.fetchCategories();
});
</script>

<template>
Expand Down Expand Up @@ -76,6 +76,15 @@ onBeforeMount(() => {
:items="taskCategoryStore.getters.taskCategories"
:fields="state.categoryFields"
>
<template #col-name-format="{ item }">
<p-link :text="item.name"
:to="{
name: makeAdminRouteName(OPS_FLOW_ROUTE.TASK_MANAGEMENT.TASK_CATEGORY.DETAIL._NAME),
params: { taskCategoryId: item.category_id }
}"
highlight
/>
</template>
<template #col-package-format="{ item }">
<p-badge shape="square"
badge-type="subtle"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<script setup lang="ts">
import { ref } from 'vue';
import DeleteModal from '@/common/components/modals/DeleteModal.vue';
import { useTaskCategoryPageStore } from '@/services/ops-flow/stores/admin/task-category-page-store';
const taskCategoryPageStore = useTaskCategoryPageStore();
const loading = ref<boolean>(false);
const handleConfirm = async () => {
loading.value = true;
// await taskCategoryPageStore.deleteStatus();
taskCategoryPageStore.closeDeleteStatusModal();
loading.value = false;
};
const handleCloseOrCancel = () => {
taskCategoryPageStore.closeDeleteStatusModal();
};
</script>

<template>
<delete-modal :visible="taskCategoryPageStore.state.visibleStatusDeleteModal"
header-title="Are you sure you want to delete this status?"
size="sm"
:loading="loading"
@confirm="handleConfirm"
@close="handleCloseOrCancel"
@cancel="handleCloseOrCancel"
/>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<script setup lang="ts">
import { PI, PBadge } from '@cloudforet/mirinae';
import type { TaskStatusType } from '@/schema/opsflow/task/type';
import ActionMenuButton from '@/common/components/buttons/ActionMenuButton.vue';
import { useTaskCategoryPageStore } from '@/services/ops-flow/stores/admin/task-category-page-store';
const props = defineProps<{
index: number;
id: string;
name: string;
color: string;
type: TaskStatusType;
}>();
const taskCategoryPageStore = useTaskCategoryPageStore();
const handleEdit = () => {
taskCategoryPageStore.openEditStatusForm(props.index, props.type);
};
const handleDelete = () => {
taskCategoryPageStore.openDeleteStatusModal(props.index, props.type);
};
</script>

<template>
<li class="task-status-draggable-item">
<p-i name="ic_drag-handle"
width="1rem"
height="1rem"
class="drag-handle"
/>
<div class="flex-grow">
<p-badge class="ml-2"
badge-type="subtle"
shape="square"
:style-type="props.color"
>
{{ props.name }}
</p-badge>
</div>
<action-menu-button @edit="handleEdit"
@delete="handleDelete"
/>
</li>
</template>

<style lang="postcss" scoped>
.task-status-draggable-item {
@apply flex items-center px-2 border border-b-0 border-gray-150;
height: 2.625rem;
&:first-of-type {
@apply rounded-t-lg;
}
&:last-of-type {
@apply rounded-b-lg border-b;
}
&.ghost {
@apply rounded-lg border;
}
>.drag-handle {
cursor: grab;
}
}
</style>
Loading

0 comments on commit 8099032

Please sign in to comment.