Skip to content

Commit

Permalink
refactor: update state management to use getters for task categories …
Browse files Browse the repository at this point in the history
…and packages (#5112)

* feat(task-category): update task status handling and modal interactions

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

* feat: add set default status modal and related functionality

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

* feat(task-type): add update fields functionality for task types

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

* feat(ops-flow): add LSB component and integrate into OpsFlowContainer

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

* feat: update lsb component structure with new navigation elements

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

* feat(ops-flow): add task category routing and new task table component

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

* refactor(task): reorganize task type store and update related components

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

* feat: refactor package and task category stores for improved state management

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

* feat(ops-flow): implement proper route location handling in BoardLSB component

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

* refactor: update state management to use getters for task categories and packages

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

* feat(ops-flow): refactor components to use script setup and improve routing

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

* feat(task-field): add dynamic task field form components and options

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

* chore(package.json): remove vite-plugin-vue-devtools dependency

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

* feat(collapsible-list): make CollapsibleItem generic for flexible data types

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

* feat: add comment section to task creation page with collapsible list

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

* feat(ops-flow): refactor task creation form and update component structure

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

* feat(task-fields): add validation for task fields and refactor components

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

* feat(task-field): make options optional for task field types and update ParagraphTaskField template

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

* refactor: remove BaseTaskField and integrate validation in templates

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

* feat(user-select-dropdown): refactor and rename assignee pool field to UserSelectDropdown component

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

* feat: add LabelsInput component for managing label inputs in forms

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

* feat(task-field): add OtherTaskField interface and update validation logic

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

* feat(date-task-field): add date validation and datetime picker component

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

* feat(task-fields): add UnknownTaskField component and update validators

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

* feat(task): add assignee field to TaskModel and update related components

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

* feat(task): implement task creation form with category and status selection

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

* feat(ops-flow): add user select dropdown for task assignee assignment

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

* feat(task-create-page): add confirmation modal for route leaving action

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

* feat(user-select-dropdown): enhance user selection with single/multiple support

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

* feat: refactor task creation components and improve routing structure

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

* feat(category-field): refactor category selection handling and validation logic

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

* feat(category-field): add task type filtering option in category selection

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

* feat(user-select-dropdown): add disabled prop and improve user ID emission handling

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

* feat(task): add task type deletion modal and integrate with store logic

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

* feat(task): refactor task creation forms and integrate new state management

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

* feat(task): implement create task with minimal mode and project field

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

* feat(user-select-dropdown): add userPool prop for filtered user selection

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

* feat(navigation): add loading spinner component for task categories loading

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

* feat: update data handling to ensure default empty arrays in stores

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

---------

Signed-off-by: Wanjin Noh <[email protected]>
  • Loading branch information
WANZARGEN authored Dec 2, 2024
1 parent f399c5e commit 98291c3
Show file tree
Hide file tree
Showing 74 changed files with 3,042 additions and 492 deletions.
118 changes: 118 additions & 0 deletions apps/web/src/common/components/inputs/LabelsInput.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<script setup lang="ts">
// CAUTION: this vOnClickOutside is using !! Please do not remove.
import { vOnClickOutside } from '@vueuse/components';
import {
watch, ref,
} from 'vue';
import {
PIconButton, PLabel, PTextInput,
} from '@cloudforet/mirinae';
import { i18n } from '@/translations';
import { useFieldValidator } from '@/common/composables/form-validator';
const props = defineProps<{
labels?: string[];
editable?: boolean;
}>();
const emit = defineEmits<{(event: 'update:labels', value: string[]): void;
}>();
const labelList = ref<string[]>([]);
const {
value, setValue, isInvalid, invalidText,
} = useFieldValidator(
'',
(val: string) => {
if (val.length > 30) return i18n.t('DASHBOARDS.CUSTOMIZE.VALIDATION_LIMITED_CHAR_LABEL');
if (labelList.value.find((d) => d === val)) return i18n.t('DASHBOARDS.CUSTOMIZE.VALIDATION_DUPLICATED_LABEL');
return '';
},
);
const inputMode = ref(false);
const isInputFocused = ref(false);
const handleClickPlus = async () => {
inputMode.value = true;
isInputFocused.value = true;
};
const handleKeyEscape = () => {
inputMode.value = false;
setValue('');
};
const handleKeyEnter = (e: KeyboardEvent) => {
if (e.isComposing || !value.value || isInvalid) return;
labelList.value.push(value.value);
setValue('');
emit('update:labels', labelList.value);
};
const handleClickDelete = (index: number) => {
labelList.value.splice(index, 1);
emit('update:labels', labelList.value);
};
watch(() => props.labels, (newLabels) => {
labelList.value = newLabels || [];
}, { immediate: true });
</script>

<template>
<div class="flex flex-wrap items-start gap-1 min-h-8"
@keydown.esc="handleKeyEscape"
@keydown.enter="handleKeyEnter"
>
<div class="flex flex-wrap items-center gap-1 min-h-6">
<p-label
v-for="(label, index) in labelList"
:key="`dashboard-label-${index}`"
:text="label"
:deletable="props.editable"
@delete="handleClickDelete(index)"
/>
<p-icon-button v-if="!inputMode && props.editable"
class="mr-1"
style-type="tertiary"
name="ic_plus_bold"
size="sm"
shape="square"
@click="handleClickPlus"
/>
<template v-if="!inputMode && !labelList.length">
<span v-if="props.editable"
class="text-gray-500 text-xs pt-1 cursor-pointer"
@click="handleClickPlus"
>
{{ $t('DASHBOARDS.CUSTOMIZE.ADD_LABEL') }}
</span>
<div v-else
class="flex items-center justify-center w-full h-20 text-gray-300"
>
{{ $t('DASHBOARDS.CUSTOMIZE.NO_LABEL') }}
</div>
</template>
</div>
<div v-if="inputMode"
v-on-click-outside="handleKeyEscape"
class="min-w-40 max-w-40"
>
<p-text-input :value="value"
:is-focused="isInputFocused"
:invalid="isInvalid"
block
size="sm"
:placeholder="$t('DASHBOARDS.CUSTOMIZE.ENTER_NEW_LABEL')"
@update:value="setValue"
@update:is-focused="isInputFocused = $event"
/>
<p v-if="isInvalid"
class="text-label-sm text-alert mt-1"
>
{{ invalidText }}
</p>
</div>
</div>
</template>
136 changes: 136 additions & 0 deletions apps/web/src/common/modules/user/UserSelectDropdown.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<script setup lang="ts">
import type { Ref } from 'vue';
import {
ref, computed, toRef, watch,
} from 'vue';
import { isEqual } from 'lodash';
import { PSelectDropdown, getTextHighlightRegex } from '@cloudforet/mirinae';
import type { AutocompleteHandler, SelectDropdownMenuItem } from '@cloudforet/mirinae/types/controls/dropdown/select-dropdown/type';
import type { UserReferenceMap } from '@/store/reference/user-reference-store';
import { useUserReferenceStore } from '@/store/reference/user-reference-store';
import ErrorHandler from '@/common/composables/error/errorHandler';
interface UserDropdownItem extends SelectDropdownMenuItem {
name: string;
label: string;
}
const props = withDefaults(defineProps<{
userId?: string;
userIds?: string[];
selectionType?: 'single'|'multiple';
useFixedMenuStyle?: boolean;
invalid?: boolean;
disabled?: boolean;
userPool?: string[];
}>(), {
userId: '',
userIds: () => [],
selectionType: 'single',
useFixedMenuStyle: false,
invalid: false,
disabled: false,
userPool: undefined,
});
const emit = defineEmits<{(event: 'update:user-ids', value: string[]): void;
(event: 'update:user-id', value: string): void;
}>();
const userReferenceStore = useUserReferenceStore();
const loading = computed(() => userReferenceStore.getters.loading);
const userReferenceMap: Ref<Readonly<UserReferenceMap>> = toRef(userReferenceStore.getters, 'userItems');
const allUserItems = computed<UserDropdownItem[]>(() => {
if (props.userPool && props.userPool.length > 0) {
return props.userPool.map((userId) => ({
name: userId,
label: userReferenceMap.value[userId]?.label ?? userId,
}));
}
return Object.values(userReferenceMap.value).map((u: UserReferenceMap[string]) => ({
name: u.key,
label: u.label,
}));
});
const selectedUserItems = ref<SelectDropdownMenuItem[]>([]);
const userMenuItemsHandler: AutocompleteHandler = async (keyword: string, pageStart = 1, pageLimit = 10) => {
const filteredItems = allUserItems.value.filter((item) => getTextHighlightRegex(keyword).test(item.label));
const _totalCount = pageStart - 1 + pageLimit;
const _slicedResults = filteredItems.slice(pageStart - 1, _totalCount);
return {
results: _slicedResults,
more: _totalCount < filteredItems.length,
};
};
const currentUserIds = computed<string[]>(() => selectedUserItems.value.map((item) => item.name));
const currentUserId = computed<string | undefined>(() => currentUserIds.value[0]);
const handleUpdateSelectedUserItems = (selectedUsers: SelectDropdownMenuItem[]) => {
if (isEqual(selectedUsers, selectedUserItems.value)) return; // prevent unnecessary update
selectedUserItems.value = selectedUsers; // it updates currentUserId and currentUserIds automatically
if (props.selectionType === 'single') {
if (currentUserId.value === props.userId) return; // prevent unnecessary update
emit('update:user-id', selectedUsers[0]?.name ?? '');
} else {
if (isEqual(currentUserIds.value, props.userIds)) return; // prevent unnecessary update
emit('update:user-ids', currentUserIds.value);
}
};
const initSingleType = (_userId?: string) => {
if (currentUserId.value === _userId) {
return;
}
selectedUserItems.value = _userId ? [{
name: _userId,
label: userReferenceMap.value[_userId]?.label ?? _userId,
}] : [];
};
const initMultipleType = (_userIds?: string[]) => {
try {
if (!Array.isArray(_userIds)) {
throw new Error('userIds should be an array');
}
if (isEqual(currentUserIds.value, _userIds)) {
return;
}
selectedUserItems.value = _userIds.map((userId) => ({
name: userId,
label: userReferenceMap.value[userId]?.label ?? userId,
selected: true,
}));
} catch (e) {
ErrorHandler.handleError(e);
}
};
watch([loading, () => props.userId, () => props.userIds], ([_loading, newUserId, newUserIds]) => {
if (_loading) return;
if (props.selectionType === 'single') {
if (currentUserId.value === newUserId) return; // prevent infinite loop
initSingleType(newUserId);
} else {
if (isEqual(currentUserIds.value, newUserIds)) return; // prevent infinite loop
initMultipleType(newUserIds);
}
}, { immediate: true });
</script>
<template>
<p-select-dropdown show-select-marker
:selected="selectedUserItems"
:handler="userMenuItemsHandler"
is-filterable
:invalid="props.invalid"
:disabled="props.disabled"
:use-fixed-menu-style="useFixedMenuStyle"
show-delete-all-button
:multi-selectable="props.selectionType === 'multiple'"
appearance-type="badge"
@update:selected="handleUpdateSelectedUserItems"
/>
</template>
19 changes: 9 additions & 10 deletions apps/web/src/schema/opsflow/_types/task-field-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ export type TaskFieldEnum = {
name: string;
};
interface TextTaskFieldOptions {
example: string;
example?: string;
}
export interface ParagraphTaskFieldOptions {
example: string;
example?: string;
}
export interface DropdownTaskFieldOptions {
enums: TaskFieldEnum[];
Expand All @@ -33,20 +33,19 @@ interface BaseTaskField {
is_required?: boolean;
is_primary?: boolean; // whether to display field during task creation
}
interface TextTaskField extends BaseTaskField {
export interface TextTaskField extends BaseTaskField {
field_type: 'TEXT';
options: TextTaskFieldOptions;
options?: TextTaskFieldOptions;
}
interface ParagraphTaskField extends BaseTaskField {
export interface ParagraphTaskField extends BaseTaskField {
field_type: 'PARAGRAPH';
options: ParagraphTaskFieldOptions;
options?: ParagraphTaskFieldOptions;
}
interface DropdownTaskField extends BaseTaskField {
export interface DropdownTaskField extends BaseTaskField {
field_type: 'DROPDOWN';
options: DropdownTaskFieldOptions;
options?: DropdownTaskFieldOptions;
}
interface OtherTaskField extends BaseTaskField {
export interface OtherTaskField extends BaseTaskField {
field_type: Exclude<TaskFieldType, 'TEXT'|'PARAGRAPH'|'DROPDOWN'>;
options: object;
}
export type TaskField = TextTaskField | ParagraphTaskField | DropdownTaskField | OtherTaskField;
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import type { Tags } from '@/schema/_common/model';
import type { TaskField } from '@/schema/opsflow/_types/task-field-type';
import type { TaskStatusOption, TaskStatusType } from '@/schema/opsflow/task/type';
import type { TaskStatusType, TaskStatusOptionWithOptionalId } from '@/schema/opsflow/task/type';

interface TaskStatusOptionWithOptionalId extends Omit<TaskStatusOption, 'status_id'> {
status_id?: string;
}

export interface TaskCategoryCreateParameters {
name: string;
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/schema/opsflow/task-category/api-verbs/update.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { Tags } from '@/schema/_common/model';
import type { TaskField } from '@/schema/opsflow/_types/task-field-type';
import type { TaskStatusOptions } from '@/schema/opsflow/task/type';
import type { TaskStatusOptions, TaskStatusOptionWithOptionalId, TaskStatusType } from '@/schema/opsflow/task/type';

export interface TaskCategoryUpdateParameters {
category_id: string;
name?: string;
description?: string;
status_options?: TaskStatusOptions;
status_options?: TaskStatusOptions|Record<TaskStatusType, TaskStatusOptionWithOptionalId[]>;
fields?: TaskField[];
force?: boolean;
tags?: Tags;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { TaskField } from '@/schema/opsflow/_types/task-field-type';

export interface TaskTypeUpdateFieldsParameters {
task_type_id: string;
fields: TaskField[];
category_id: string;
force: boolean;
}
2 changes: 0 additions & 2 deletions apps/web/src/schema/opsflow/task-type/api-verbs/update.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import type { Tags } from '@/schema/_common/model';
import type { TaskField } from '@/schema/opsflow/_types/task-field-type';

export interface TaskTypeUpdateParameters {
task_type_id: string;
name?: string;
description?: string;
fields?: TaskField[];
assignee_pool?: string[];
tags?: Tags;
category_id?: string;
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/schema/opsflow/task/api-verbs/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { TaskPriority } from '@/schema/opsflow/task/type';
export interface TaskCreateParameters {
task_type_id: string;
name: string;
status: string;
status_id: string;
priority?: TaskPriority;
description?: string;
files?: FileModel[];
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/schema/opsflow/task/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface TaskModel {
description: string;
data: Record<string, any>;
files: FileModel[];
assignee: string;
task_type_id: string;
category_id: string;
project_id: string;
Expand Down
5 changes: 4 additions & 1 deletion apps/web/src/schema/opsflow/task/type.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import type { TASK_STATUS_COLOR_NAMES } from '@/schema/opsflow/task/constant';

export type TaskStatusType = 'TODO'|'IN_PROGRESS'|'COMPLETED';
type TaskStatusColorName = typeof TASK_STATUS_COLOR_NAMES[number];
export type TaskStatusColorName = typeof TASK_STATUS_COLOR_NAMES[number];
export interface TaskStatusOption {
status_id: string;
name: string;
color: TaskStatusColorName;
is_default?: boolean;
}
export type TaskStatusOptions = Record<TaskStatusType, TaskStatusOption[]>;
export interface TaskStatusOptionWithOptionalId extends Omit<TaskStatusOption, 'status_id'> {
status_id?: string;
}
export type TaskPriority = 'LOW'|'MEDIUM'|'HIGH';
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ export default defineComponent({
instance.setPathFrom(from);
});
},
beforeRouteLeave(to, from, next) {
next((vm) => {
const instance = vm as unknown as IInstance;
if (!instance.isConfirmBackModalVisible.value) {
instance.isConfirmBackModalVisible.value = true;
instance.nextRoute.value = to;
}
});
},
});
</script>

Expand Down
Loading

0 comments on commit 98291c3

Please sign in to comment.