Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions src/components/common/EditInformation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,15 @@
class="hidden" />
</div>
<div class="flex flex-col relative">
<p class="text-body text-xs font-semibold">이름</p>
<span class="absolute top-1 right-2 text-xs text-gray-500"> {{ name.length }} / 10 </span>
<div class="flex items-center gap-1 text-red-1">
<p class="text-body text-xs font-semibold">이름</p>
<p>*</p>
<span
v-show="isInvalid || isFull"
class="text-xs font-semibold"
>{{ nameError }}</span
>
</div>
<input
:class="[
'block w-full px-4 py-4 border rounded focus:outline-none h-11 mt-2',
Expand All @@ -68,13 +75,7 @@
maxlength="10"
ref="nameInput"
@blur="validateName" />
<div class="mb-1">
<span
v-show="isInvalid || isFull"
class="absolute text-red-1 text-xs font-semibold mt-1"
>{{ nameError }}</span
>
</div>
<span class="mt-1.5 text-xs text-gray-500"> {{ name.length }} / 10 </span>
</div>
<div class="flex flex-col">
<p class="text-body text-xs font-semibold">아이디</p>
Expand Down
3 changes: 2 additions & 1 deletion src/components/common/ModalView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@

<script setup lang="ts">
import { failIcon, successIcon, warningIcon } from '@/constants/iconPath'
import { preventEnter } from '@/utils/preventEnter'
import { onUnmounted, ref, watch } from 'vue'
import CommonIcons from './CommonIcons.vue'
import { preventEnter } from '@/utils/preventEnter'

const { isOpen, type, modelValue } = defineProps<{
isOpen: boolean
Expand Down Expand Up @@ -116,6 +116,7 @@ watch(
() => isOpen,
() => {
if (isOpen) {
textValue.value = ''
document.body.style.overflow = 'hidden'
window.addEventListener('keydown', preventEnter)
} else {
Expand Down
8 changes: 5 additions & 3 deletions src/components/request-approve/RequestApprove.vue
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@
import { getMainCategory, getSubCategory } from '@/api/common'
import { getTaskDetailUser, postTaskApprove } from '@/api/user'
import { INITIAL_REQUEST_APPROVE_DATA } from '@/constants/manager'
import { useErrorStore } from '@/stores/error'
import type { Category, SubCategory } from '@/types/common'
import { convertToISO, isAfterNow } from '@/utils/date'
import { redirectToLogin } from '@/utils/redirectToLogin'
import { computed, onMounted, ref, watch } from 'vue'
import { onBeforeRouteLeave, useRouter } from 'vue-router'
import FormButtonContainer from '../common/FormButtonContainer.vue'
Expand All @@ -71,8 +73,6 @@ import CategoryDropDown from '../request-task/CategoryDropDown.vue'
import DueDateInput from './DueDateInput.vue'
import LabelDropdown from './LabelDropdown.vue'
import ManagerDropdown from './ManagerDropdown.vue'
import { useErrorStore } from '@/stores/error'
import { redirectToLogin } from '@/utils/redirectToLogin'

const isModalVisible = ref(false)
const category1 = ref<Category | null>(null)
Expand Down Expand Up @@ -157,10 +157,12 @@ const handleSubmit = async () => {
isInvalidate.value = ''
return
}
console.log(isTimeFilled.value, isTimeComplete.value, isDueDateValid.value)

const requestData = {
categoryId: category2.value.subCategoryId,
processorId: approveData.value.processor.memberId,
dueDate: !isTimeFilled.value
dueDate: isTimeFilled.value
? convertToISO(approveData.value.dueDate, approveData.value.dueTime)
: null,
labelId: approveData.value.label?.labelId || null
Expand Down
19 changes: 9 additions & 10 deletions src/components/request-task/ReRequestTask.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
v-model="title"
:placeholderText="'제목을 입력해주세요'"
:label-name="'제목'"
:limit-length="30"
:is-invalidate="isInvalidate" />
<RequestTaskTextArea
v-model="description"
:is-invalidate="isInvalidate"
:placeholderText="'부가 정보를 입력해주세요'" />
:placeholderText="'부가 정보를 입력해주세요'"
:limit-length="200" />
<RequestTaskFileInput v-model="file" />
<FormButtonContainer
:handleCancel="handleCancel"
Expand Down Expand Up @@ -173,15 +175,12 @@ const handleSubmit = async () => {
})
}

try {
if (reqType === 're') {
await postTaskRequest(formData)
} else {
await patchTaskRequest(id, formData)
}
isModalVisible.value = 'success'
} finally {
isSubmitting.value = false
if (reqType === 're') {
await postTaskRequest(formData)
} else {
await patchTaskRequest(id, formData)
}
isModalVisible.value = 'success'
isSubmitting.value = false
}
</script>
15 changes: 7 additions & 8 deletions src/components/request-task/RequestTask.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@
v-model="title"
:placeholderText="'제목을 입력해주세요'"
:label-name="'제목'"
:is-invalidate="isInvalidate" />
:is-invalidate="isInvalidate"
:limit-length="30" />
<RequestTaskTextArea
v-model="description"
:is-invalidate="isInvalidate"
:placeholderText="'부가 정보를 입력해주세요'" />
:placeholderText="'부가 정보를 입력해주세요'"
:limit-length="200" />
<RequestTaskFileInput v-model="file" />
<FormButtonContainer
:handleCancel="handleCancel"
Expand Down Expand Up @@ -139,11 +141,8 @@ const handleSubmit = async () => {
if (file.value && file.value.length > 0) {
file.value.forEach(f => formData.append('attachment', f))
}
try {
await postTaskRequest(formData)
isModalVisible.value = 'success'
} finally {
isSubmitting.value = false
}
await postTaskRequest(formData)
isModalVisible.value = 'success'
isSubmitting.value = false
}
</script>
2 changes: 2 additions & 0 deletions src/components/request-task/RequestTaskFileInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ const handleFileUpload = (event: Event) => {
}

const handleDrop = (event: DragEvent) => {
event.preventDefault()
isDragging.value = false
const files = event.dataTransfer?.files
if (files && files.length > 0) {
const newFiles = Array.from(files).filter(file => file.size <= 5 * 1024 * 1024)
Expand Down
19 changes: 17 additions & 2 deletions src/components/request-task/RequestTaskInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,20 @@
<p class="text-body font-semibold">{{ labelName }}</p>
<p v-if="!isNotRequired">*</p>
<p v-if="isInvalidateState === 'input'">{{ labelName }}을 입력해주세요</p>
<p v-if="isInvalidateState === 'nameEmpty' && labelName === '이름'">
{{ labelName }}을 입력해주세요
</p>
<p v-if="isInvalidateState === 'duplicate'">회원아이디가 중복되었습니다</p>
<p v-if="isInvalidateState === 'title'">제목은 30자 이내로 적어주세요</p>
<p v-if="isInvalidateState === 'noCode'">작업코드를 입력해주세요</p>
<p v-if="isInvalidateState === 'code'">사용할 수 없는 작업코드입니다</p>
<p v-if="isInvalidateState === 'categoryName'">카테고리명을 입력해주세요</p>
<p v-if="isInvalidateState === 'wrongNickname'">잘못된 형식의 아이디입니다</p>
<p v-if="isInvalidateState === 'wrongNickname' && labelName === '아이디'">
잘못된 형식의 아이디입니다.
</p>
<p v-if="isInvalidateState === 'wrongEmail' && labelName === '도메인'">
잘못된 형식의 도메인입니다.
</p>
</div>
<input
class="w-full h-11 border border-border-1 px-4 focus:outline-none rounded"
Expand All @@ -19,13 +27,18 @@
:placeholder="placeholderText"
:class="{ 'text-gray-500': isEdit }"
:maxlength="labelName === '제목' ? 30 : undefined" />
<p
v-if="limitLength"
class="text-xs mt-1.5">
({{ inputLength }}/{{ limitLength }})
</p>
</div>
</template>

<script lang="ts" setup>
import type { RequestTaskInputProps } from '@/types/user'
import { computed } from 'vue'
const { modelValue, placeholderText, labelName, isNotRequired, isEdit, isInvalidate } =
const { modelValue, placeholderText, labelName, isNotRequired, isEdit, isInvalidate, limitLength } =
defineProps<RequestTaskInputProps>()

const isInvalidateState = computed(() => isInvalidate)
Expand All @@ -35,4 +48,6 @@ const emit = defineEmits(['update:modelValue'])
const updateValue = (value: string) => {
emit('update:modelValue', value)
}

const inputLength = computed(() => modelValue.length)
</script>
14 changes: 11 additions & 3 deletions src/components/request-task/RequestTaskTextArea.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<div class="flex flex-col gap-2">
<div class="text-xs flex gap-x-1 mb-2 text-red-1">
<div class="flex flex-col gap-1.5">
<div class="text-xs flex gap-x-1 text-red-1">
<p class="text-body text-xs font-semibold">부가설명</p>
<p v-if="isInvalidateState === 'description'">부가설명은 200자 이내로 적어주세요</p>
</div>
Expand All @@ -11,18 +11,26 @@
:maxlength="200"
@input="updateValue(($event.target as HTMLInputElement).value)">
</textarea>
<p
v-if="limitLength"
class="text-xs">
({{ inputLength }}/{{ limitLength }})
</p>
</div>
</template>

<script lang="ts" setup>
import type { RequestTaskTextAreaProps } from '@/types/user'
import { computed } from 'vue'

const { modelValue, placeholderText, isInvalidate } = defineProps<RequestTaskTextAreaProps>()
const { modelValue, placeholderText, isInvalidate, limitLength } =
defineProps<RequestTaskTextAreaProps>()
const emit = defineEmits(['update:modelValue'])
const isInvalidateState = computed(() => isInvalidate)

const updateValue = (value: string) => {
emit('update:modelValue', value)
}

const inputLength = computed(() => modelValue.length)
</script>
2 changes: 1 addition & 1 deletion src/components/requested/RequestedListCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ const rejectRequest = async () => {
modalError.value = '반려 사유를 입력해주세요'
return
}
await axiosInstance.patch(`/api/tasks/${info.taskId}/terminate`, rejectReason)
await axiosInstance.patch(`/api/tasks/${info.taskId}/terminate`, { reason: rejectReason.value })
toggleModal('success')
}

Expand Down
4 changes: 3 additions & 1 deletion src/components/task-detail/TaskDetailHistory.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@
</div>
<div class="flex w-full flex-col items-center">
<TaskDetailHistoryChat
v-if="item.taskHistoryType === 'COMMENT'"
v-if="
item.taskHistoryType === 'COMMENT' || item.taskHistoryType === 'TASK_TERMINATED'
"
:history="item"
:requestor-name="requestorName"
:task-id="taskId" />
Expand Down
2 changes: 1 addition & 1 deletion src/components/task-detail/TaskDetailHistoryChat.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
'flex max-w-[400px] flex-wrap px-4 py-3 text-base rounded-lg font-normal',
isRequestor ? 'bg-background-2' : 'bg-primary2'
]">
{{ history.details.commentDetails?.comment }}
{{ history.details.commentDetails?.comment || history.details.taskDetails?.value }}
</p>
<div
:class="[
Expand Down
4 changes: 2 additions & 2 deletions src/components/task-detail/TaskDetailHistoryFile.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
</div>
</div>
<div>
{{ formatOnlyTime(history.time) }}
{{ formatTimeShort(history.time) }}
</div>
</div>
</div>
Expand All @@ -72,7 +72,7 @@ import { deleteComment } from '@/api/user'
import { fileIcon, menuDotIcon } from '@/constants/iconPath'
import { useMemberStore } from '@/stores/member'
import type { TaskDetailHistoryChatProps } from '@/types/common'
import { formatOnlyTime } from '@/utils/date'
import { formatTimeShort } from '@/utils/date'
import { useQueryClient } from '@tanstack/vue-query'
import { storeToRefs } from 'pinia'
import { computed, defineProps, ref } from 'vue'
Expand Down
2 changes: 1 addition & 1 deletion src/components/task-detail/TaskStatusList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ const rejectRequest = async () => {
modalError.value = '종료 사유를 입력해주세요'
return
}
await axiosInstance.patch(`/api/tasks/${taskId}/terminate`, rejectReason)
await axiosInstance.patch(`/api/tasks/${taskId}/terminate`, { reason: rejectReason.value })
toggleModal('success')
emit('update:status', 'TERMINATED')
currentStatus.value = 'TERMINATED'
Expand Down
13 changes: 12 additions & 1 deletion src/components/user-manage/UserRegistration.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
</ModalView>
<RequestTaskInput
v-model="userRegistrationForm.name"
:is-invalidate="isInvalidate"
:placeholderText="'회원의 이름을 입력해주세요'"
:labelName="'이름'" />
<RequestTaskInput
Expand All @@ -23,8 +24,9 @@
:labelName="'이메일'" />
<RequestTaskInput
v-model="userRegistrationForm.email"
:placeholderText="'@kakao.com'"
:placeholderText="'@kakaocorp.com'"
:label-name="'도메인'"
:is-invalidate="isInvalidate"
:is-not-required="false" />
</div>
<DepartmentDropDown
Expand Down Expand Up @@ -85,13 +87,22 @@ const handleCancel = () => {
}

const usernameRegex = /^[a-z]{3,10}\.[a-z]{1,5}$/
const emailRegex = /^@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$/

const handleSubmit = async () => {
try {
if (!userRegistrationForm.value.name) {
isInvalidate.value = 'nameEmpty'
return
}
if (!usernameRegex.test(userRegistrationForm.value.nickname)) {
isInvalidate.value = 'wrongNickname'
return
}
if (!emailRegex.test(userRegistrationForm.value.email)) {
isInvalidate.value = 'wrongEmail'
return
}
const formData = {
...userRegistrationForm.value,
isReviewer: isManager.value ? userRegistrationForm.value.isReviewer : false,
Expand Down
2 changes: 2 additions & 0 deletions src/types/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export interface RequestTaskInputProps {
isEdit?: boolean
isInvalidate?: string
isDisbled?: boolean
limitLength?: number
}

export interface RequestTaskFileInputProps {
Expand All @@ -43,6 +44,7 @@ export interface RequestTaskTextAreaProps {
modelValue: string
placeholderText: string
isInvalidate?: string
limitLength?: number
}

export interface AttachmentResponse {
Expand Down
9 changes: 0 additions & 9 deletions src/utils/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,6 @@ export const formatDateAndTime = (dateString: string) => {
return `${year}.${month}.${day} ${period} ${hours}시 ${minutes}분`
}

export const formatOnlyTime = (timeString: string) => {
const [hours, minutes] = timeString.split(':').map(Number)

const period = hours < 12 ? '오전' : '오후'
const formattedHours = hours % 12 || 12

return `${period} ${formattedHours}시 ${String(minutes).padStart(2, '0')}분`
}

export const formatTimeAgo = (createdAt: string) => {
const now = new Date()
const createdDate = new Date(createdAt)
Expand Down
2 changes: 1 addition & 1 deletion src/views/LoginView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ const handleLogin = async () => {
'로그인 시도 5회 초과로 계정이 정지되었습니다\n30분 후 다시 시도해주세요'
} else {
messageHeader.value = '일치하는 정보가 없습니다'
messageBody.value = '닉네임과 비밀번호를 다시 확인해주세요'
messageBody.value = '아이디와 비밀번호를 다시 확인해주세요'
}
isModalVisible.value = !isModalVisible.value
break
Expand Down