-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: update state management to use getters for task categories …
…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
Showing
74 changed files
with
3,042 additions
and
492 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
136
apps/web/src/common/modules/user/UserSelectDropdown.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 2 additions & 2 deletions
4
apps/web/src/schema/opsflow/task-category/api-verbs/update.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
apps/web/src/schema/opsflow/task-type/api-verbs/update-fields.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.